3 Commits

Author SHA1 Message Date
John Alanbrook
c014f29ce7 fill out box2d methods
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-linux (push) Failing after 1m30s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
2025-05-25 01:58:10 -05:00
John Alanbrook
f1b3e5eddc Merge branch 'master' into box2d
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-24 22:45:52 -05:00
John Alanbrook
2594c03765 initial box2d integration
All checks were successful
Build and Deploy / build-linux (push) Successful in 1m12s
Build and Deploy / build-windows (CLANG64) (push) Successful in 9m33s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
2025-02-26 07:47:04 -06:00
703 changed files with 31489 additions and 111600 deletions

View File

@@ -1,19 +0,0 @@
sdl_video = "main"
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master"
[system]
ar_timer = 60
actor_memory = 0
net_service = 0.1
reply_timeout = 60
actor_max = "10_000"
stack_max = 0
[actors]
[actors.prosperon/sdl_video]
main = true
[actors.prosperon/prosperon]
main = true
[actors.prosperon]
main = true
[actors.accio]
main=true

View File

@@ -1,6 +0,0 @@
[modules]
[modules.extramath]
hash = "MCLZT3JABTAENS4WVXKGWJ7JPBLZER4YQ5VN2PE7ZD2Z4WYGTIMA===="
url = "https://gitea.pockle.world/john/extramath@master"
downloaded = "Monday June 2 12:07:20.42 PM -5 2025 AD"
commit = "84d81a19a8455bcf8dc494739e9e6d545df6ff2c"

View File

@@ -1,12 +0,0 @@
# Use the official Emscripten SDK image (includes emcc, emsdk, Python, nodejs, etc)
FROM emscripten/emsdk:latest
# Install Meson & Ninja if needed (some emsdk tags already bundle them)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
meson \
ninja-build \
python3-pip && \
rm -rf /var/lib/apt/lists/*
RUN pip3 install --upgrade meson>=1.4

View File

@@ -28,7 +28,5 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ccache \
mingw-w64 \
wine \
libmimalloc-dev \
npm nodejs zip
RUN apt-get install -y libunwind-dev libblas-dev liblapacke-dev
npm nodejs zip && \
rm -rf /var/lib/apt/lists/*

View File

@@ -1,6 +1,4 @@
FROM debian:trixie
ENV DEBIAN_FRONTEND=noninteractive
FROM ubuntu:plucky
RUN apt-get update && \
apt-get install -y --no-install-recommends \
@@ -13,11 +11,5 @@ RUN apt-get update && \
pkg-config \
zip \
ccache \
npm \
nodejs \
meson \
libmimalloc-dev \
libbsd-dev \
gcc-mingw-w64-ucrt64 \
g++-mingw-w64-ucrt64 && \
npm nodejs && \
rm -rf /var/lib/apt/lists/*

13
.gitignore vendored
View File

@@ -6,18 +6,27 @@ build/
*.o
*.a
*.d
tags
Jenkinsfile
*~
*.log
*.gz
*.tar
.nova/
packer*
primum
sokol-shdc*
source/shaders/*.h
core.cdb
primum.exe
core.cdb.h
jsc
.DS_Store
*.html
.vscode
*.icns
game.zip
icon.ico
steam/
subprojects/*/
build_dbg/
modules/
build_dbg/

View File

@@ -13,6 +13,7 @@ This is a game engine developed using a QuickJS fork as its scripting language.
## Coding Practices
- Use K&R style C
- Use as little whitespace as possible
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
## Instructions

View File

@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Build Commands
### Build variants
- `make` - Make and install debug version. Usually all that's needed.
- `make debug` - Build debug version (uses meson debug configuration)
- `make fast` - Build optimized version
- `make release` - Build release version with LTO and optimizations
- `make small` - Build minimal size version
@@ -13,10 +13,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `make crosswin` - Cross-compile for Windows using mingw32
### Testing
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
## Scripting language
This is called "cell", a variant of JavaScript with important differences. See docs/cell.md for detailed language documentation.
- `meson test -C build_dbg` - Run all tests in debug build
- `meson test -C build_<variant>` - Run tests in specific build variant
- `./build_dbg/prosperon tests/<testname>.js` - Run specific test
- Available tests: `spawn_actor`, `empty`, `nota`, `wota`, `portalspawner`, `overling`, `send`, `delay`
### Common development commands
- `meson setup build_<variant>` - Configure build directory
@@ -34,16 +34,12 @@ Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty sy
- Hierarchical actor system with spawning/killing
- Actor lifecycle: awake, update, draw, garbage collection
### Cell Language Style Guide
### JavaScript Style Guide
- Use `use()` function for imports (Misty-style, not ES6 import/export)
- Prefer closures and javascript objects and prototypes over ES6 style classes
- Follow existing JavaScript patterns in the codebase
- Functions as first-class citizens
- Use `def` for constants (not const)
- Use `var` for variables (block-scoped like let)
- Check for null with `== null` (no undefined in Cell)
- Use `==` for equality (always strict, no `===`)
- See docs/cell.md for complete language reference
- Do not use const or let; only var
### Core Systems
1. **Actor System** (scripts/core/engine.js)
@@ -103,7 +99,7 @@ cd examples/chess
- Documentation is found in docs
- Documentation for the JS modules loaded with 'use' is docs/api/modules
- .md files directly in docs gives a high level overview
- docs/cell.md documents the Cell language (JavaScript variant used in Prosperon)
- docs/dull is what this specific Javascript system is (including alterations from quickjs/es6)
### Shader Development
- Shaders are in `shaders/` directory as HLSL
@@ -130,7 +126,7 @@ meson test -C build_dbg
### Debugging
- Use debug build: `make debug`
- Tracy profiler support when enabled
- Console logging available via `log.console()`, `log.error()`, etc.
- Console logging available via `console.log()`, `console.error()`, etc.
- Log files written to `.prosperon/log.txt`
# Project Structure Notes
@@ -206,11 +202,6 @@ meson test -C build_dbg
### Utility Modules
- `time` - Time management and delays
- **Must be imported with `use('time')`**
- No `time.now()` function - use:
- `time.number()` - Number representation of current time
- `time.record()` - Struct representation of current time
- `time.text()` - Text representation of current time
- `io` - File I/O operations
- `json` - JSON parsing and serialization
- `util` - General utilities
@@ -243,7 +234,7 @@ When sending a message with a callback, respond by sending to the message itself
```javascript
// Sender side:
send(actor, {type: 'status'}, response => {
log.console(response); // Handle the response
console.log(response); // Handle the response
});
// Receiver side:
@@ -288,7 +279,7 @@ $_.receiver(msg => {
- Custom formats: Aseprite animations, etc.
### Developer Tools
- Built-in documentation system with `cell.DOC`
- Built-in documentation system with `prosperon.DOC`
- Tracy profiler integration for performance monitoring
- Imgui debugging tools
- Console logging with various severity levels

View File

@@ -3,7 +3,6 @@ FROM ubuntu:plucky AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip \
libmimalloc-dev \
libasound2-dev \
libpulse-dev \
libudev-dev \
@@ -32,7 +31,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
WORKDIR /app
RUN git clone https://gitea.pockle.world/john/prosperon.git
WORKDIR /app/prosperon
RUN git checkout master
RUN git checkout jsffi_refactor
RUN meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
RUN meson compile -C build

View File

@@ -1,23 +1,22 @@
debug: FORCE
meson setup build_dbg -Dbuildtype=debugoptimized
meson install --only-changed -C build_dbg
cp build_dbg/cell . && chmod +x cell
meson setup build_dbg -Dbuildtype=debug
meson compile -C build_dbg
fast: FORCE
meson setup build_fast
meson install -C build_fast
meson compile -C build_fast
release: FORCE
meson setup -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true build_release
meson install -C build_release
meson compile -C build_release
sanitize: FORCE
meson setup -Db_sanitize=address -Db_sanitize=memory -Db_sanitize=leak -Db_sanitize=undefined build_sani
meson install -C build_sani
meson compile -C build_sani
small: FORCE
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
meson install -C build_small
meson compile -C build_small
web: FORCE
meson setup -Deditor=false -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true --cross-file emscripten.cross build_web
@@ -28,43 +27,3 @@ crosswin: FORCE
meson compile -C build_win
FORCE:
IMAGE_LINUX := prosperon/linux-builder:latest
IMAGE_MINGW := prosperon/mingw-builder:latest
IMAGE_EMSCRIPTEN := prosperon/emscripten-builder:latest
PWD := $(shell pwd)
ARTIFACTS_DIR := artifacts
build:
meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
meson compile -C build
dockerclean:
rm -rf build build-win $(ARTIFACTS_DIR)
dockerlinux: build-linux-image run-linux
build-linux-image:
docker build -f .github/docker/Dockerfile.linux -t $(IMAGE_LINUX) .
run-linux:
@mkdir -p $(ARTIFACTS_DIR)/linux
docker run --rm -v $(PWD):/src -w /src $(IMAGE_LINUX) bash -lc 'meson setup build -Dbuildtype=release -Db_lto=true -Db_ndebug=true && meson compile -C build && cp build/cell $(ARTIFACTS_DIR)/linux/'
dockerwin: build-mingw-image run-win
build-mingw-image:
docker build -f .github/docker/Dockerfile.mingw -t $(IMAGE_MINGW) .
run-win:
@mkdir -p $(ARTIFACTS_DIR)/windows
docker run --rm -v $(PWD):/src -w /src $(IMAGE_MINGW) bash -lc 'meson setup build-win --cross-file mingw32.cross -Dbuildtype=release -Db_lto=true -Db_ndebug=true && meson compile -C build-win && cp build-win/cell.exe $(ARTIFACTS_DIR)/windows/'
dockeremc: build-emscripten-image run-emc
build-emscripten-image:
docker build -f .github/docker/Dockerfile.emscripten -t $(IMAGE_EMSCRIPTEN) .
run-emc:
@mkdir -p $(ARTIFACTS_DIR)/emscripten
docker run --rm -v $(PWD):/src -w /src $(IMAGE_EMSCRIPTEN) bash -lc 'meson setup build-emscripten --cross-file emscripten.cross -Dbuildtype=release -Db_ndebug=true -Ddefault_library=static -Dcpp_std=c++11 && meson compile -C build-emscripten && cp build-emscripten/cell.wasm build-emscripten/cell.js $(ARTIFACTS_DIR)/emscripten/'

View File

@@ -1,43 +0,0 @@
function mainThread() {
var maxDepth = Math.max(6, Number(arg[0] || 16));
var stretchDepth = maxDepth + 1;
var check = itemCheck(bottomUpTree(stretchDepth));
log.console(`stretch tree of depth ${stretchDepth}\t check: ${check}`);
var longLivedTree = bottomUpTree(maxDepth);
for (let depth = 4; depth <= maxDepth; depth += 2) {
var iterations = 1 << maxDepth - depth + 4;
work(iterations, depth);
}
log.console(`long lived tree of depth ${maxDepth}\t check: ${itemCheck(longLivedTree)}`);
}
function work(iterations, depth) {
let check = 0;
for (let i = 0; i < iterations; i++)
check += itemCheck(bottomUpTree(depth));
log.console(`${iterations}\t trees of depth ${depth}\t check: ${check}`);
}
function TreeNode(left, right) {
return {left, right};
}
function itemCheck(node) {
if (node.left == null)
return 1;
return 1 + itemCheck(node.left) + itemCheck(node.right);
}
function bottomUpTree(depth) {
return depth > 0
? new TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
: new TreeNode(null, null);
}
mainThread()
$_.stop()

View File

@@ -1,24 +0,0 @@
var blob = use('blob')
function eratosthenes (n) {
var sieve = new blob(n, true)
var sqrtN = Math.trunc(Math.sqrt(n));
for (i = 2; i <= sqrtN; i++)
if (sieve.read_logical(i))
for (j = i * i; j <= n; j += i)
sieve.write_bit(j, false);
return sieve;
}
var sieve = eratosthenes(10000000);
stone(sieve)
var c = 0
for (var i = 0; i < sieve.length; i++)
if (sieve.read_logical(i)) c++
log.console(c)
$_.stop()

View File

@@ -1,58 +0,0 @@
function fannkuch(n) {
var perm1 = [n]
for (let i = 0; i < n; i++) perm1[i] = i
var perm = [n]
var count = [n]
var f = 0, flips = 0, nperm = 0, checksum = 0
var i, k, r
r = n
while (r > 0) {
i = 0
while (r != 1) { count[r-1] = r; r -= 1 }
while (i < n) { perm[i] = perm1[i]; i += 1 }
// Count flips and update max and checksum
f = 0
k = perm[0]
while (k != 0) {
i = 0
while (2*i < k) {
let t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
i += 1
}
k = perm[0]
f += 1
}
if (f > flips) flips = f
if ((nperm & 0x1) == 0) checksum += f; else checksum -= f
// Use incremental change to generate another permutation
var more = true
while (more) {
if (r == n) {
log.console( checksum )
return flips
}
let p0 = perm1[0]
i = 0
while (i < r) {
let j = i + 1
perm1[i] = perm1[j]
i = j
}
perm1[r] = p0
count[r] -= 1
if (count[r] > 0) more = false; else r += 1
}
nperm += 1
}
return flips;
}
var n = arg[0] || 10
log.console(`Pfannkuchen(${n}) = ${fannkuch(n)}`)
$_.stop()

View File

@@ -1,16 +0,0 @@
var time = use('time')
function fib(n) {
if (n<2) return n
return fib(n-1) + fib(n-2)
}
var now = time.number()
var arr = [1,2,3,4,5]
for (var i in arr) {
log.console(fib(28))
}
log.console(`elapsed: ${time.number()-now}`)
$_.stop()

View File

@@ -1,20 +0,0 @@
#!/bin/bash
# Run hyperfine with parameter lists
# This will create a cross-product of all libraries × all scenarios
hyperfine \
--warmup 3 \
--runs 20 \
-i \
--export-csv wota_vs_nota_vs_json.csv \
--export-json wota_vs_nota_vs_json.json \
--export-markdown wota_vs_nota_vs_json.md \
--parameter-list lib wota,nota,json \
--parameter-list scen empty,integers,floats,strings,objects,nested,large_array \
'cell benchmarks/wota_nota_json {lib} {scen}'
echo "Benchmark complete! Results saved to:"
echo " - wota_vs_nota_vs_json.csv"
echo " - wota_vs_nota_vs_json.json"
echo " - wota_vs_nota_vs_json.md"

View File

@@ -1,395 +0,0 @@
var time = use('time')
////////////////////////////////////////////////////////////////////////////////
// JavaScript Performance Benchmark Suite
// Tests core JS operations: property access, function calls, arithmetic, etc.
////////////////////////////////////////////////////////////////////////////////
// Test configurations
const iterations = {
simple: 10000000,
medium: 1000000,
complex: 100000
};
////////////////////////////////////////////////////////////////////////////////
// Utility: measureTime(fn) => how long fn() takes in seconds
////////////////////////////////////////////////////////////////////////////////
function measureTime(fn) {
var start = time.number();
fn();
var end = time.number();
return (end - start);
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Property Access
////////////////////////////////////////////////////////////////////////////////
function benchPropertyAccess() {
var obj = {
a: 1, b: 2, c: 3, d: 4, e: 5,
nested: { x: 10, y: 20, z: 30 }
};
var readTime = measureTime(function() {
var sum = 0;
for (var i = 0; i < iterations.simple; i++) {
sum += obj.a + obj.b + obj.c + obj.d + obj.e;
sum += obj.nested.x + obj.nested.y + obj.nested.z;
}
});
var writeTime = measureTime(function() {
for (var i = 0; i < iterations.simple; i++) {
obj.a = i;
obj.b = i + 1;
obj.c = i + 2;
obj.nested.x = i * 2;
obj.nested.y = i * 3;
}
});
return { readTime: readTime, writeTime: writeTime };
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Function Calls
////////////////////////////////////////////////////////////////////////////////
function benchFunctionCalls() {
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
function complexCalc(a, b, c) { return (a + b) * c / 2; }
var obj = {
method: function(x) { return x * 2; },
nested: {
deepMethod: function(x, y) { return x + y; }
}
};
var simpleCallTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.simple; i++) {
result = add(i, 1);
result = multiply(result, 2);
}
});
var methodCallTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.simple; i++) {
result = obj.method(i);
result = obj.nested.deepMethod(result, i);
}
});
var complexCallTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.medium; i++) {
result = complexCalc(i, i + 1, i + 2);
}
});
return {
simpleCallTime: simpleCallTime,
methodCallTime: methodCallTime,
complexCallTime: complexCallTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Array Operations
////////////////////////////////////////////////////////////////////////////////
function benchArrayOps() {
var pushTime = measureTime(function() {
var arr = [];
for (var i = 0; i < iterations.medium; i++) {
arr.push(i);
}
});
var arr = [];
for (var i = 0; i < 10000; i++) arr.push(i);
var accessTime = measureTime(function() {
var sum = 0;
for (var i = 0; i < iterations.medium; i++) {
sum += arr[i % 10000];
}
});
var iterateTime = measureTime(function() {
var sum = 0;
for (var j = 0; j < 1000; j++) {
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
});
return {
pushTime: pushTime,
accessTime: accessTime,
iterateTime: iterateTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Object Creation
////////////////////////////////////////////////////////////////////////////////
function benchObjectCreation() {
var literalTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var obj = { x: i, y: i * 2, z: i * 3 };
}
});
function Point(x, y) {
this.x = x;
this.y = y;
}
var constructorTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var p = new Point(i, i * 2);
}
});
var protoObj = {
x: 0,
y: 0,
move: function(dx, dy) {
this.x += dx;
this.y += dy;
}
};
var prototypeTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var obj = Object.create(protoObj);
obj.x = i;
obj.y = i * 2;
}
});
return {
literalTime: literalTime,
constructorTime: constructorTime,
prototypeTime: prototypeTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: String Operations
////////////////////////////////////////////////////////////////////////////////
function benchStringOps() {
var concatTime = measureTime(function() {
var str = "";
for (var i = 0; i < iterations.complex; i++) {
str = "test" + i + "value";
}
});
var strings = [];
for (var i = 0; i < 1000; i++) {
strings.push("string" + i);
}
var joinTime = measureTime(function() {
for (var i = 0; i < iterations.complex; i++) {
var result = strings.join(",");
}
});
var splitTime = measureTime(function() {
var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
for (var i = 0; i < iterations.medium; i++) {
var parts = str.split(",");
}
});
return {
concatTime: concatTime,
joinTime: joinTime,
splitTime: splitTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Arithmetic Operations
////////////////////////////////////////////////////////////////////////////////
function benchArithmetic() {
var intMathTime = measureTime(function() {
var result = 1;
for (var i = 0; i < iterations.simple; i++) {
result = ((result + i) * 2 - 1) / 3;
result = result % 1000 + 1;
}
});
var floatMathTime = measureTime(function() {
var result = 1.5;
for (var i = 0; i < iterations.simple; i++) {
result = Math.sin(result) + Math.cos(i * 0.01);
result = Math.sqrt(Math.abs(result)) + 0.1;
}
});
var bitwiseTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.simple; i++) {
result = (result ^ i) & 0xFFFF;
result = (result << 1) | (result >> 15);
}
});
return {
intMathTime: intMathTime,
floatMathTime: floatMathTime,
bitwiseTime: bitwiseTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Closure Operations
////////////////////////////////////////////////////////////////////////////////
function benchClosures() {
function makeAdder(x) {
return function(y) { return x + y; };
}
var closureCreateTime = measureTime(function() {
var funcs = [];
for (var i = 0; i < iterations.medium; i++) {
funcs.push(makeAdder(i));
}
});
var adders = [];
for (var i = 0; i < 1000; i++) {
adders.push(makeAdder(i));
}
var closureCallTime = measureTime(function() {
var sum = 0;
for (var i = 0; i < iterations.medium; i++) {
sum += adders[i % 1000](i);
}
});
return {
closureCreateTime: closureCreateTime,
closureCallTime: closureCallTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Main benchmark runner
////////////////////////////////////////////////////////////////////////////////
log.console("JavaScript Performance Benchmark");
log.console("======================\n");
// Property Access
log.console("BENCHMARK: Property Access");
var propResults = benchPropertyAccess();
log.console(" Read time: " + propResults.readTime.toFixed(3) + "s => " +
(iterations.simple / propResults.readTime).toFixed(1) + " reads/sec [" +
(propResults.readTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Write time: " + propResults.writeTime.toFixed(3) + "s => " +
(iterations.simple / propResults.writeTime).toFixed(1) + " writes/sec [" +
(propResults.writeTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Function Calls
log.console("BENCHMARK: Function Calls");
var funcResults = benchFunctionCalls();
log.console(" Simple calls: " + funcResults.simpleCallTime.toFixed(3) + "s => " +
(iterations.simple / funcResults.simpleCallTime).toFixed(1) + " calls/sec [" +
(funcResults.simpleCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Method calls: " + funcResults.methodCallTime.toFixed(3) + "s => " +
(iterations.simple / funcResults.methodCallTime).toFixed(1) + " calls/sec [" +
(funcResults.methodCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Complex calls: " + funcResults.complexCallTime.toFixed(3) + "s => " +
(iterations.medium / funcResults.complexCallTime).toFixed(1) + " calls/sec [" +
(funcResults.complexCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Array Operations
log.console("BENCHMARK: Array Operations");
var arrayResults = benchArrayOps();
log.console(" Push: " + arrayResults.pushTime.toFixed(3) + "s => " +
(iterations.medium / arrayResults.pushTime).toFixed(1) + " pushes/sec [" +
(arrayResults.pushTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Access: " + arrayResults.accessTime.toFixed(3) + "s => " +
(iterations.medium / arrayResults.accessTime).toFixed(1) + " accesses/sec [" +
(arrayResults.accessTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Iterate: " + arrayResults.iterateTime.toFixed(3) + "s => " +
(1000 / arrayResults.iterateTime).toFixed(1) + " full iterations/sec");
log.console("");
// Object Creation
log.console("BENCHMARK: Object Creation");
var objResults = benchObjectCreation();
log.console(" Literal: " + objResults.literalTime.toFixed(3) + "s => " +
(iterations.medium / objResults.literalTime).toFixed(1) + " creates/sec [" +
(objResults.literalTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Constructor: " + objResults.constructorTime.toFixed(3) + "s => " +
(iterations.medium / objResults.constructorTime).toFixed(1) + " creates/sec [" +
(objResults.constructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Prototype: " + objResults.prototypeTime.toFixed(3) + "s => " +
(iterations.medium / objResults.prototypeTime).toFixed(1) + " creates/sec [" +
(objResults.prototypeTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
// String Operations
log.console("BENCHMARK: String Operations");
var strResults = benchStringOps();
log.console(" Concat: " + strResults.concatTime.toFixed(3) + "s => " +
(iterations.complex / strResults.concatTime).toFixed(1) + " concats/sec [" +
(strResults.concatTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
log.console(" Join: " + strResults.joinTime.toFixed(3) + "s => " +
(iterations.complex / strResults.joinTime).toFixed(1) + " joins/sec [" +
(strResults.joinTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
log.console(" Split: " + strResults.splitTime.toFixed(3) + "s => " +
(iterations.medium / strResults.splitTime).toFixed(1) + " splits/sec [" +
(strResults.splitTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Arithmetic Operations
log.console("BENCHMARK: Arithmetic Operations");
var mathResults = benchArithmetic();
log.console(" Integer math: " + mathResults.intMathTime.toFixed(3) + "s => " +
(iterations.simple / mathResults.intMathTime).toFixed(1) + " ops/sec [" +
(mathResults.intMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Float math: " + mathResults.floatMathTime.toFixed(3) + "s => " +
(iterations.simple / mathResults.floatMathTime).toFixed(1) + " ops/sec [" +
(mathResults.floatMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Bitwise: " + mathResults.bitwiseTime.toFixed(3) + "s => " +
(iterations.simple / mathResults.bitwiseTime).toFixed(1) + " ops/sec [" +
(mathResults.bitwiseTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Closures
log.console("BENCHMARK: Closures");
var closureResults = benchClosures();
log.console(" Create: " + closureResults.closureCreateTime.toFixed(3) + "s => " +
(iterations.medium / closureResults.closureCreateTime).toFixed(1) + " creates/sec [" +
(closureResults.closureCreateTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Call: " + closureResults.closureCallTime.toFixed(3) + "s => " +
(iterations.medium / closureResults.closureCallTime).toFixed(1) + " calls/sec [" +
(closureResults.closureCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
log.console("---------------------------------------------------------");
log.console("Benchmark complete.\n");
$_.stop()

View File

@@ -1,40 +0,0 @@
var blob = use('blob')
var iter = 50, limit = 2.0;
var zr, zi, cr, ci, tr, ti;
var h = Number(arg[0]) || 500
var w = h
log.console(`P4\n${w} ${h}`);
for (let y = 0; y < h; ++y) {
// Create a blob for the row - we need w bits
var row = new blob(w);
for (let x = 0; x < w; ++x) {
zr = zi = tr = ti = 0;
cr = 2 * x / w - 1.5;
ci = 2 * y / h - 1;
for (let i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
zi = 2 * zr * zi + ci;
zr = tr - ti + cr;
tr = zr * zr;
ti = zi * zi;
}
// Write a 1 bit if inside the set, 0 if outside
if (tr + ti <= limit * limit)
row.write_bit(1);
else
row.write_bit(0);
}
// Convert the blob to stone (immutable) to prepare for output
stone(row)
// Output the blob data as raw bytes
log.console(text(row, 'b'));
}
$_.stop()

View File

@@ -1,11 +0,0 @@
var N = 1000000;
var num = 0;
for (var i = 0; i < N; i ++) {
var x = 2 * $_.random();
var y = $_.random();
if (y < Math.sin(x * x))
num++;
}
log.console(2 * num / N);
$_.stop()

View File

@@ -1,188 +0,0 @@
var PI = Math.PI;
var SOLAR_MASS = 4 * PI * PI;
var DAYS_PER_YEAR = 365.24;
function Body(x, y, z, vx, vy, vz, mass) {
this.x = x;
this.y = y;
this.z = z;
this.vx = vx;
this.vy = vy;
this.vz = vz;
this.mass = mass;
}
function Jupiter() {
return new Body(
4.84143144246472090e+00,
-1.16032004402742839e+00,
-1.03622044471123109e-01,
1.66007664274403694e-03 * DAYS_PER_YEAR,
7.69901118419740425e-03 * DAYS_PER_YEAR,
-6.90460016972063023e-05 * DAYS_PER_YEAR,
9.54791938424326609e-04 * SOLAR_MASS
);
}
function Saturn() {
return new Body(
8.34336671824457987e+00,
4.12479856412430479e+00,
-4.03523417114321381e-01,
-2.76742510726862411e-03 * DAYS_PER_YEAR,
4.99852801234917238e-03 * DAYS_PER_YEAR,
2.30417297573763929e-05 * DAYS_PER_YEAR,
2.85885980666130812e-04 * SOLAR_MASS
);
}
function Uranus() {
return new Body(
1.28943695621391310e+01,
-1.51111514016986312e+01,
-2.23307578892655734e-01,
2.96460137564761618e-03 * DAYS_PER_YEAR,
2.37847173959480950e-03 * DAYS_PER_YEAR,
-2.96589568540237556e-05 * DAYS_PER_YEAR,
4.36624404335156298e-05 * SOLAR_MASS
);
}
function Neptune() {
return new Body(
1.53796971148509165e+01,
-2.59193146099879641e+01,
1.79258772950371181e-01,
2.68067772490389322e-03 * DAYS_PER_YEAR,
1.62824170038242295e-03 * DAYS_PER_YEAR,
-9.51592254519715870e-05 * DAYS_PER_YEAR,
5.15138902046611451e-05 * SOLAR_MASS
);
}
function Sun() {
return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
}
var bodies = Array(Sun(), Jupiter(), Saturn(), Uranus(), Neptune());
function offsetMomentum() {
var px = 0;
var py = 0;
var pz = 0;
var size = bodies.length;
for (var i = 0; i < size; i++) {
var body = bodies[i];
var mass = body.mass;
px += body.vx * mass;
py += body.vy * mass;
pz += body.vz * mass;
}
var body = bodies[0];
body.vx = -px / SOLAR_MASS;
body.vy = -py / SOLAR_MASS;
body.vz = -pz / SOLAR_MASS;
}
function advance(dt) {
var size = bodies.length;
for (var i = 0; i < size; i++) {
var bodyi = bodies[i];
var vxi = bodyi.vx;
var vyi = bodyi.vy;
var vzi = bodyi.vz;
for (var j = i + 1; j < size; j++) {
var bodyj = bodies[j];
var dx = bodyi.x - bodyj.x;
var dy = bodyi.y - bodyj.y;
var dz = bodyi.z - bodyj.z;
var d2 = dx * dx + dy * dy + dz * dz;
var mag = dt / (d2 * Math.sqrt(d2));
var massj = bodyj.mass;
vxi -= dx * massj * mag;
vyi -= dy * massj * mag;
vzi -= dz * massj * mag;
var massi = bodyi.mass;
bodyj.vx += dx * massi * mag;
bodyj.vy += dy * massi * mag;
bodyj.vz += dz * massi * mag;
}
bodyi.vx = vxi;
bodyi.vy = vyi;
bodyi.vz = vzi;
}
for (var i = 0; i < size; i++) {
var body = bodies[i];
body.x += dt * body.vx;
body.y += dt * body.vy;
body.z += dt * body.vz;
}
}
function energy() {
var e = 0;
var size = bodies.length;
for (var i = 0; i < size; i++) {
var bodyi = bodies[i];
e += 0.5 * bodyi.mass * ( bodyi.vx * bodyi.vx +
bodyi.vy * bodyi.vy + bodyi.vz * bodyi.vz );
for (var j = i + 1; j < size; j++) {
var bodyj = bodies[j];
var dx = bodyi.x - bodyj.x;
var dy = bodyi.y - bodyj.y;
var dz = bodyi.z - bodyj.z;
var distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
e -= (bodyi.mass * bodyj.mass) / distance;
}
}
return e;
}
var n = arg[0] || 100000
offsetMomentum();
log.console(`n = ${n}`)
log.console(energy().toFixed(9))
for (var i = 0; i < n; i++)
advance(0.01);
log.console(energy().toFixed(9))
var js = use('js')
// Get function metadata
var fn_info = js.fn_info(advance)
log.console(`${fn_info.filename}:${fn_info.line}:${fn_info.column}: function: ${fn_info.name}`)
// Display arguments
if (fn_info.args && fn_info.args.length > 0) {
log.console(` args: ${fn_info.args.join(' ')}`)
}
// Display local variables
if (fn_info.locals && fn_info.locals.length > 0) {
log.console(' locals:')
for (var i = 0; i < fn_info.locals.length; i++) {
var local = fn_info.locals[i]
log.console(` ${local.index}: ${local.type} ${local.name}`)
}
}
// Display stack size
log.console(` stack_size: ${fn_info.stack_size}`)
// Display disassembly
log.console(json.encode(js.disassemble(advance)))
log.console(js.disassemble(advance).length)
$_.stop()

View File

@@ -1,76 +0,0 @@
var nota = use('nota')
var os = use('os')
var io = use('io')
var ll = io.slurp('benchmarks/nota.json')
var newarr = []
var accstr = ""
for (var i = 0; i < 10000; i++) {
accstr += i;
newarr.push(i.toString())
}
// Arrays to store timing results
var jsonDecodeTimes = [];
var jsonEncodeTimes = [];
var notaEncodeTimes = [];
var notaDecodeTimes = [];
var notaSizes = [];
// Run 100 tests
for (let i = 0; i < 100; i++) {
// JSON Decode test
let start = os.now();
var jll = json.decode(ll);
jsonDecodeTimes.push((os.now() - start) * 1000);
// JSON Encode test
start = os.now();
let jsonStr = JSON.stringify(jll);
jsonEncodeTimes.push((os.now() - start) * 1000);
// NOTA Encode test
start = os.now();
var nll = nota.encode(jll);
notaEncodeTimes.push((os.now() - start) * 1000);
// NOTA Decode test
start = os.now();
var oll = nota.decode(nll);
notaDecodeTimes.push((os.now() - start) * 1000);
}
// Calculate statistics
function getStats(arr) {
def avg = arr.reduce((a, b) => a + b) / arr.length;
def min = Math.min(...arr);
def max = Math.max(...arr);
return { avg, min, max };
}
// Pretty print results
log.console("\n== Performance Test Results (100 iterations) ==");
log.console("\nJSON Decoding (ms):");
def jsonDecStats = getStats(jsonDecodeTimes);
log.console(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
log.console(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
log.console(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
log.console("\nJSON Encoding (ms):");
def jsonEncStats = getStats(jsonEncodeTimes);
log.console(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
log.console(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
log.console(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
log.console("\nNOTA Encoding (ms):");
def notaEncStats = getStats(notaEncodeTimes);
log.console(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
log.console(`Min: ${notaEncStats.min.toFixed(2)} ms`);
log.console(`Max: ${notaEncStats.max.toFixed(2)} ms`);
log.console("\nNOTA Decoding (ms):");
def notaDecStats = getStats(notaDecodeTimes);
log.console(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
log.console(`Min: ${notaDecStats.min.toFixed(2)} ms`);
log.console(`Max: ${notaDecStats.max.toFixed(2)} ms`);

76
benchmarks/nota.js Normal file
View File

@@ -0,0 +1,76 @@
var nota = use('nota')
var os = use('os')
var io = use('io')
var ll = io.slurp('benchmarks/nota.json')
var newarr = []
var accstr = ""
for (var i = 0; i < 10000; i++) {
accstr += i;
newarr.push(i.toString())
}
// Arrays to store timing results
var jsonDecodeTimes = [];
var jsonEncodeTimes = [];
var notaEncodeTimes = [];
var notaDecodeTimes = [];
var notaSizes = [];
// Run 100 tests
for (let i = 0; i < 100; i++) {
// JSON Decode test
let start = os.now();
var jll = json.decode(ll);
jsonDecodeTimes.push((os.now() - start) * 1000);
// JSON Encode test
start = os.now();
let jsonStr = JSON.stringify(jll);
jsonEncodeTimes.push((os.now() - start) * 1000);
// NOTA Encode test
start = os.now();
var nll = nota.encode(jll);
notaEncodeTimes.push((os.now() - start) * 1000);
// NOTA Decode test
start = os.now();
var oll = nota.decode(nll);
notaDecodeTimes.push((os.now() - start) * 1000);
}
// Calculate statistics
function getStats(arr) {
const avg = arr.reduce((a, b) => a + b) / arr.length;
const min = Math.min(...arr);
const max = Math.max(...arr);
return { avg, min, max };
}
// Pretty print results
console.log("\n=== Performance Test Results (100 iterations) ===");
console.log("\nJSON Decoding (ms):");
const jsonDecStats = getStats(jsonDecodeTimes);
console.log(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
console.log(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
console.log(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
console.log("\nJSON Encoding (ms):");
const jsonEncStats = getStats(jsonEncodeTimes);
console.log(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
console.log(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
console.log(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
console.log("\nNOTA Encoding (ms):");
const notaEncStats = getStats(notaEncodeTimes);
console.log(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
console.log(`Min: ${notaEncStats.min.toFixed(2)} ms`);
console.log(`Max: ${notaEncStats.max.toFixed(2)} ms`);
console.log("\nNOTA Decoding (ms):");
const notaDecStats = getStats(notaDecodeTimes);
console.log(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
console.log(`Min: ${notaDecStats.min.toFixed(2)} ms`);
console.log(`Max: ${notaDecStats.max.toFixed(2)} ms`);

View File

@@ -1,50 +0,0 @@
function A(i,j) {
return 1/((i+j)*(i+j+1)/2+i+1);
}
function Au(u,v) {
for (var i=0; i<u.length; ++i) {
var t = 0;
for (var j=0; j<u.length; ++j)
t += A(i,j) * u[j];
v[i] = t;
}
}
function Atu(u,v) {
for (var i=0; i<u.length; ++i) {
var t = 0;
for (var j=0; j<u.length; ++j)
t += A(j,i) * u[j];
v[i] = t;
}
}
function AtAu(u,v,w) {
Au(u,w);
Atu(w,v);
}
function spectralnorm(n) {
var i, u=[], v=[], w=[], vv=0, vBv=0;
for (i=0; i<n; ++i)
u[i] = 1; v[i] = w[i] = 0;
for (i=0; i<10; ++i) {
AtAu(u,v,w);
AtAu(v,u,w);
}
for (i=0; i<n; ++i) {
vBv += u[i]*v[i];
vv += v[i]*v[i];
}
return Math.sqrt(vBv/vv);
}
log.console(spectralnorm(arg[0]).toFixed(9));
$_.stop()

View File

@@ -36,7 +36,7 @@ function roundTripWota(value) {
// iterations: how many times to loop
//
// You can tweak these as you like for heavier or lighter tests.
def benchmarks = [
const benchmarks = [
{
name: "Small Integers",
data: [0, 42, -1, 2023],
@@ -75,8 +75,8 @@ def benchmarks = [
];
// Print a header
log.console("Wota Encode/Decode Benchmark");
log.console("===================\n");
console.log("Wota Encode/Decode Benchmark");
console.log("============================\n");
// We'll run each benchmark scenario in turn.
for (let bench of benchmarks) {
@@ -96,11 +96,11 @@ for (let bench of benchmarks) {
let elapsedSec = measureTime(runAllData, bench.iterations);
let opsPerSec = (totalIterations / elapsedSec).toFixed(1);
log.console(`${bench.name}:`);
log.console(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
log.console(` Elapsed: ${elapsedSec.toFixed(3)} s`);
log.console(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
console.log(`${bench.name}:`);
console.log(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
console.log(` Elapsed: ${elapsedSec.toFixed(3)} s`);
console.log(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
}
// All done
log.console("Benchmark completed.\n");
console.log("Benchmark completed.\n");

View File

@@ -2,53 +2,43 @@
// benchmark_wota_nota_json.js
//
// Usage in QuickJS:
// qjs benchmark_wota_nota_json.js <LibraryName> <ScenarioName>
// qjs benchmark_wota_nota_json.js
//
// Ensure wota, nota, json, and os are all available, e.g.:
var wota = use('wota');
var nota = use('nota');
var json = use('json');
var jswota = use('jswota')
var os = use('os');
//
// Parse command line arguments
if (arg.length != 2) {
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');
$_.stop()
}
var lib_name = arg[0];
var scenario_name = arg[1];
////////////////////////////////////////////////////////////////////////////////
// 1. Setup "libraries" array to easily switch among wota, nota, and json
// 1. Setup "libraries" array to easily switch among Wota, Nota, and JSON
////////////////////////////////////////////////////////////////////////////////
def libraries = [
const libraries = [
{
name: "wota",
name: "Wota",
encode: wota.encode,
decode: wota.decode,
// wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
// Wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
getSize(encoded) {
return encoded.length;
return encoded.byteLength;
}
},
{
name: "nota",
name: "Nota",
encode: nota.encode,
decode: nota.decode,
// nota also produces an ArrayBuffer:
// Nota also produces an ArrayBuffer:
getSize(encoded) {
return encoded.length;
return encoded.byteLength;
}
},
{
name: "json",
name: "JSON",
encode: json.encode,
decode: json.decode,
// json produces a JS string. We'll measure its UTF-16 code unit length
// JSON produces a JS string. We'll measure its UTF-16 code unit length
// as a rough "size". Alternatively, you could convert to UTF-8 for
// a more accurate byte size. Here we just use `string.length`.
getSize(encodedStr) {
@@ -62,29 +52,24 @@ def libraries = [
// Each scenario has { name, data, iterations }
////////////////////////////////////////////////////////////////////////////////
def benchmarks = [
const benchmarks = [
{
name: "empty",
data: [{}, {}, {}, {}],
iterations: 10000
},
{
name: "integers",
name: "Small Integers",
data: [0, 42, -1, 2023],
iterations: 100000
},
{
name: "floats",
name: "Floating point",
data: [0.1, 1e-50, 3.14159265359],
iterations: 100000
},
{
name: "strings",
data: ["Hello, wota!", "short", "Emoji: \u{1f600}\u{1f64f}"],
name: "Strings (short, emoji)",
data: ["Hello, Wota!", "short", "Emoji: \u{1f600}\u{1f64f}"],
iterations: 100000
},
{
name: "objects",
name: "Small Objects",
data: [
{ a:1, b:2.2, c:"3", d:false },
{ x:42, y:null, z:"test" }
@@ -92,15 +77,20 @@ def benchmarks = [
iterations: 50000
},
{
name: "nested",
name: "Nested Arrays",
data: [ [ [ [1,2], [3,4] ] ], [[[]]], [1, [2, [3, [4]]]] ],
iterations: 50000
},
{
name: "large_array",
name: "Large Array (1k integers)",
data: [ Array.from({length:1000}, (_, i) => i) ],
iterations: 1000
},
{
name: "Large Binary Blob (256KB)",
data: [ new Uint8Array(256 * 1024).buffer ],
iterations: 200
}
];
////////////////////////////////////////////////////////////////////////////////
@@ -118,7 +108,7 @@ function measureTime(fn) {
// 4. For each library, we run each benchmark scenario and measure:
// - Encoding time (seconds)
// - Decoding time (seconds)
// - Total encoded size (bytes or code units for json)
// - Total encoded size (bytes or code units for JSON)
//
////////////////////////////////////////////////////////////////////////////////
@@ -135,11 +125,11 @@ function runBenchmarkForLibrary(lib, bench) {
let encodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) {
// For each data item, encode it
for (let j = 0; j < bench.data.length; j++) {
let e = lib.encode(bench.data[j]);
for (let d of bench.data) {
let e = lib.encode(d);
// store only in the very first iteration, so we can decode them later
// but do not store them every iteration or we blow up memory.
if (i == 0) {
if (i === 0) {
encodedList.push(e);
totalSize += lib.getSize(e);
}
@@ -162,43 +152,31 @@ function runBenchmarkForLibrary(lib, bench) {
}
////////////////////////////////////////////////////////////////////////////////
// 5. Main driver: run only the specified library and scenario
// 5. Main driver: run across all benchmarks, for each library.
////////////////////////////////////////////////////////////////////////////////
// Find the requested library and scenario
var lib = libraries.find(l => l.name == lib_name);
var bench = benchmarks.find(b => b.name == scenario_name);
console.log("Benchmark: Wota vs Nota vs JSON");
console.log("================================\n");
if (!lib) {
log.console('Unknown library:', lib_name);
log.console('Available libraries:', libraries.map(l => l.name).join(', '));
$_.stop()
for (let bench of benchmarks) {
console.log(`SCENARIO: ${bench.name}`);
console.log(` Data length: ${bench.data.length} | Iterations: ${bench.iterations}\n`);
for (let lib of libraries) {
let { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
// We'll compute total operations = bench.iterations * bench.data.length
let totalOps = bench.iterations * bench.data.length;
let encOpsPerSec = (totalOps / encodeTime).toFixed(1);
let decOpsPerSec = (totalOps / decodeTime).toFixed(1);
console.log(` ${lib.name}:`);
console.log(` Encode time: ${encodeTime.toFixed(3)}s => ${encOpsPerSec} encodes/sec`);
console.log(` Decode time: ${decodeTime.toFixed(3)}s => ${decOpsPerSec} decodes/sec`);
console.log(` Total size: ${totalSize} bytes (or code units for JSON)`);
console.log("");
}
console.log("---------------------------------------------------------\n");
}
if (!bench) {
log.console('Unknown scenario:', scenario_name);
log.console('Available scenarios:', benchmarks.map(b => b.name).join(', '));
$_.stop()
}
// Run the benchmark for this library/scenario combination
var { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
// Output json for easy parsing by hyperfine or other tools
var totalOps = bench.iterations * bench.data.length;
var result = {
lib: lib_name,
scenario: scenario_name,
encodeTime: encodeTime,
decodeTime: decodeTime,
totalSize: totalSize,
totalOps: totalOps,
encodeOpsPerSec: totalOps / encodeTime,
decodeOpsPerSec: totalOps / decodeTime,
encodeNsPerOp: (encodeTime / totalOps) * 1e9,
decodeNsPerOp: (decodeTime / totalOps) * 1e9
};
log.console(json.encode(result));
$_.stop()
console.log("Benchmark complete.\n");

164
cell.md
View File

@@ -1,164 +0,0 @@
JAVASCRIPT VISION
I see objects as being a sort of combination of a lisp cell and a record: symbols, which are used internally, and are private and non iterable, and record string values, which are iterable, readable, and writable; of course everything becomes locked in when stone.
CELLSCRIPT
Javascript to its core. Objects. What does the language need? It can be quite small, I think. The key is, ANYTHING that we want to be fast and JIT'd, must be present. So, record lookups. These are actually quicker in a jit'd language that have them as a feature. Most things should be libraries. Blobs need to be in the runtime.
## Actors and Objects
Actors have a unique memory space and are made up of many objects. Objects are created in the Self style, but with a limitation: only one parent.
Actors only communicate with messages. Messages are a record of data consisting of a few base types: text, numbers, arrays, records, boolean values. There is no RPC, and it is not recommended to build it into your message passing protocol. Messages are very high level things: "do X", which the actor can then go and carry out.
Cell provides a fast way to condense an object for sending.
## How is it different from Javascript?
Cell condenses Javascript down into a few core ideas. There are three pillars which cell relies on:
1. The idea of actors as a method of communication between parts of a program.
2. The idea of objects as a way to organize and encapsulate data.
3. The idea of the capability model as security.
Javascript already supplied some of these things; Cell takes the core of Javascript and makes these ideas more explicit, and layers on the actor communication. It removes some goofy suckiness with javascript.
It acts as something like an operating system at the application level. It allows random code to be ran on your machine without worrying it will break something. This is built into the language.
It is completly dynamically typed. In comparison with C, in C, you can treat everything as everything: it is almost not typed at all. If you try to use a type as another type, no error is thrown; it might work, but it mightly silently not work. In Cell, data has a hard type, but if you use it "incorrectly", it will throw, and you can correct it. It's a live system.
Cell is linked very closely with C. It's best to think of cell as a layer for message passing on top of C. It is a way to describe how to translate C tasks from one section of the program to another - or to totally different computers (actors).
As such, cell's primary duty is marshalling data; so it has been designed for that to be as fast as possible. It has a syntax similar to C to make it easy to translate formulae from cell to C (or the other way, if desired).
Unlike many actor languages, Cell does not eschew assignment. You must have some assignment. However, when it comes to actor->actor communication, you do not assign. RPC is too direct: one actor should not care all that much what specific functions another actor has available. It should request it to do something, and get a result, or possibly not get a result. It doesn't care what the actor does as long as that gets done.
But within itself, it will assign; it must. Actors, or cells, are best thought of as computers or nodes within the internet. You request data from a URL by typing it into your browser; that computer you're attempting to reach may not even be on. It very likely has written some other data to disk whenever you contact it. But you're not doing the specific assigning. You just request data with HTTP commands.
## Objects and actors
Objects and actors are both similar ideas: they can hold data and respond to messages. Objects, local to an actor, can be thought of more like an RPC idea: they're invoked and return immediately. However, a failed RPC can crash an object; and in that case, the actor halts. It can be corrected.
## What does Cell bring you over C?
Programs which are built with C; they're built statically; they're built to not crash; they're built doing extremely low level things, like assignment.
The goal of cell is to thrust your C code into the parallel, actor realm. It lets your code crash and resume it; even rewriting the C code which is butressing your cell code and reloading it live.
There are two primary sorts of Cell modules you create from C code: data and IO. C code like
Where there were two similar things in javscript, one has been deleted and one kept. For example, there is only null now, no undefined. There are not four ways to test for equality; there is one.
The purpose of this is to be a great language for passing messages. So it should be fast at creating records first and foremost, and finding items on them. So it needs first class, jitt'd records.
Finally, it needs to use less memory. Deleting a bunch of this stuff should make that simpler.
What is present?
Objects, prototypes, numbers, arrays, strings, true, false, null.
Things to do:
merge typeof and instanceof. Misty has array? stone? number? etc; it needs to be generic. 5 is number returns true.
No new operator. It's the same idea though: simply instead of 'var guy = new sprite({x,y})' you would say 'var guy = sprite({x,y})', and sprite would simply be a function written to return a sprite object.
One number type. Dec64. Numeric stack can be added in later: a bigint library, for example, built inside cell.
Simplify the property attributes stuff. It is simple: objects have text keys and whatever values. Objects can also have objects as values. These work like symbols. You can share them, if desired. No well known symbols exist to eliminate that much misdirection. Obejcts basically work like private keys. If you serialize an object, objects that are keys are not serialized; only textual keys are. You can do something about it with a json() method that is invoked, if you desire. You cannot retrieve
var works like let; use var instead of let
no const
Function closures and _ => all work the same and close over the 'this' variable
Totally delete modules, coroutines, generators, proxy .. this deletes a lot of the big switch statement
Add the 'go' statement for tail calls
Add the 'do' statement
Implementation detail: separate out arrays and objects. They are not the same. Objects no longer need to track if they're fast arrays or not. They're not. Arrays are. Always.
Add the functional proxy idea. Log will be implemented through that.
Remove ===; it's just == now, and !=.
Remove 'continue'; now, break handles both. For a do statement, label it, and break to that label; so
var x = 0
do loop {
x++
if (x < 5) break loop // goes back to loop
break // exits loop
}
rename instanceof to 'is'
remove undefined; all are 'null' now
remove 'delete'; to remove a field, assign it to null
remove with
Remove Object. New records have a prototype of nothing. There are no more 'type prototypes' at all.
Arrays are their own type
Remove property descriptors. Properties are always settable, unless the object as a whole is stone. Stone is an object property instead of a shape property.
Syntax stuff .. would like to invoke functions without (). This can effectively simulate a "getter". Make ? and all other characters usable for names. No reserve words, which are endlessly irritating.
----
This will all actually come about gradually. Add a few things at a time, fix up code that did not adhere. For a lot of this, no new functions will even need to be written; it's a matter of not calling certain functions that are no longer relevant, or calling different functions when required.
## Benchmarks to implement
### general speed
binarytrees
coro-prime-sieve
edigits
fannkuch-redux
fasta
http-server
json serialize/deserialize
knucleotide
lru
mandelbrot
merkletrees
nbody
nsieve
pidigits
regex-redux
secp256k1
spectral-norm
### function calling and recursion stress - test goto
naive recursive fibonacci [fib(35) or fib(40)]
tak
ackermann
### numeric
sieve of eratosthenes [10^7 bits]
spectral norm [5500 x 5500 matrix]
n-body sim [50 000 - 100 000 steps]
mandelbrot [1600x1200 image, max iter = 50]
### memory & gc torture
binary trees [depth 18 (~500 000 nodes)]
richards task scheduler
fannkuch redux [n=11 or 12]
### dynamic object & property access
deltablue constraint solver
splay tree [256k nodes]
json, wota, nota decode->encode [use 2MB example]
### string / regex kernels
regex-DNA
fasta
word-frequency
### concurrency/message passing
ping-pong [two actors exhange a small record N times, 1M messages end to end]
chameneos [mating color swap game w/ randezvous]
For all, track memory and time.

View File

@@ -47,7 +47,7 @@ Certain functions are intrinsic to the program and cannot be overridden. They
- **Example**:
```js
this.delay(_ => {
log.console("3 seconds later!")
console.log("3 seconds later!")
}, 3)
```

View File

@@ -3,11 +3,11 @@
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".
Docstrings are set to the symbol `cell.DOC`
Docstrings are set to the symbol `prosperon.DOC`
```js
// Suppose we have a module that returns a function
function greet(name) { log.console("Hello, " + name) }
function greet(name) { console.log("Hello, " + name) }
// We can attach a docstring
greet.doc = `
@@ -21,12 +21,12 @@ return greet
```js
// Another way is to add a docstring object to an object
var greet = {
hello() { log.console('hello!') }
hello() { console.log('hello!') }
}
greet[cell.DOC] = {}
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
greet[prosperon.DOC] = {}
greet[prosperon.DOC][prosperon.DOC] = 'An object full of different greeter functions'
greet[prosperon.DOC].hello = 'A greeter that says, "hello!"'
```

View File

@@ -1,180 +0,0 @@
# Cell Language
Cell is a JavaScript variant used in the Prosperon game engine. While very similar to JavaScript, it has several important differences that make it more suitable for game development and actor-based programming.
## Key Differences from JavaScript
### Null vs Undefined
- Cell has only `null`, no `undefined`
- Idiomatic null checking: `if (object.x == null)`
- Uninitialized variables and missing properties return `null`
### Equality Operators
- Only `==` operator exists (no `===`)
- `==` is always strict (no type coercion)
- `!=` for inequality (no `!==`)
### Variable Declarations
- `def` keyword for constants (replaces `const`)
- `var` works like `let` (block-scoped)
- No `let` keyword
### Compilation
- All code is compiled in strict mode
- No need for `"use strict"` directive
### Removed Features
Cell removes several JavaScript features for simplicity and security:
- No `Proxy` objects
- No ES6 module syntax (use `use()` function instead)
- No `class` syntax (use prototypes and closures)
- No `Reflect` API
- No `BigInt`
- No `WeakMap`, `WeakSet`, `WeakRef`
- No `document.all` (HTMLAllCollection)
- No `with` statement
- No `Date` intrinsic (use `time` module instead)
## Language Features
### Constants
```javascript
def PI = 3.14159
def MAX_PLAYERS = 4
// PI = 3.14 // Error: cannot reassign constant
```
### Variables
```javascript
var x = 10
{
var y = 20 // Block-scoped like let
x = 15 // Can access outer scope
}
// y is not accessible here
```
### Null Checking
```javascript
var obj = {name: "player"}
if (obj.score == null) {
obj.score = 0
}
```
### Functions
```javascript
// Function declaration
function add(a, b) {
return a + b
}
// Function expression
var multiply = function(a, b) {
return a * b
}
// Arrow functions work normally
var square = x => x * x
```
### Objects and Prototypes
```javascript
// Object creation
var player = {
x: 0,
y: 0,
move: function(dx, dy) {
this.x += dx
this.y += dy
}
}
// Prototype-based inheritance
function Enemy(x, y) {
this.x = x
this.y = y
}
Enemy.prototype.attack = function() {
// Attack logic
}
```
### Module System
Cell uses a custom module system with the `use()` function:
```javascript
var math = use('math')
var draw2d = use('prosperon/draw2d')
```
### Time Handling
Since there's no `Date` object, use the `time` module:
```javascript
var time = use('time')
var now = time.number() // Numeric timestamp
var record = time.record() // Structured time
var text = time.text() // Human-readable time
```
## Best Practices
1. **Prefer `def` for values that won't change**
```javascript
def TILE_SIZE = 32
var playerPos = {x: 0, y: 0}
```
2. **Always check for null explicitly**
```javascript
if (player.weapon == null) {
player.weapon = createDefaultWeapon()
}
```
3. **Use prototype patterns instead of classes**
```javascript
function GameObject(x, y) {
this.x = x
this.y = y
}
GameObject.prototype.update = function(dt) {
// Update logic
}
```
4. **Leverage closures for encapsulation**
```javascript
function createCounter() {
var count = 0
return {
increment: function() { count++ },
getValue: function() { return count }
}
}
```
## Common Gotchas
1. **No undefined means different behavior**
```javascript
var obj = {}
console.log(obj.missing) // null, not undefined
```
2. **Strict equality by default**
```javascript
"5" == 5 // false (no coercion)
null == 0 // false
```
3. **Block-scoped var**
```javascript
for (var i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 100) // Works as expected
}
```
## See Also
- [Actor Model](actor.md) - Cell's actor-based programming model
- [Module System](modules.md) - How to use and create modules
- [API Reference](api/index.md) - Complete API documentation

View File

@@ -49,7 +49,7 @@ return {
This will cause prosperon to launch a 500x500 window with the title 'Hello World'. In your ```main.js```, write the following:
```
log.console("Hello world")
console.log("Hello world")
this.delay(_ => {
this.kill();
@@ -62,6 +62,6 @@ this.delay(_ => {
The global object called `prosperon` has a variety of engine specific settings on it that can be set to influence how the engine behaves. For example, `prosperon.argv` contains a list of the command line arguments given to prosperon; `prosperon.PATH` is an array of paths to resolve resources such as modules and images. `prosperon` is fully documented in the API section.
## Getting help
The `prosperon` global has a 'doc' function, which can be invoked on any engine object to see a description of it and its members. For example, to learn about `prosperon`, try printing out `cell.DOC(prosperon)` in your `main.js`.
The `prosperon` global has a 'doc' function, which can be invoked on any engine object to see a description of it and its members. For example, to learn about `prosperon`, try printing out `prosperon.doc(prosperon)` in your `main.js`.
Writing documentation for your own modules and game components will be explored in the chapter on actors & modules.

View File

@@ -1,22 +1,22 @@
[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
pkgconfig = 'pkg-config'
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
pkg-config = 'pkg-config'
exe_wrapper = 'node'
[host_machine]
system = 'emscripten'
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'
cpu = 'wasm32'
endian = 'little'
[built-in options]
pkg_config_path = '/emsdk/upstream/emscripten/cache/sysroot/lib/pkgconfig'
cmake_prefix_path = '/emsdk/upstream/emscripten/cache/sysroot'
pkg_config_path = '$EMSDK/upstream/emscripten/cache/sysroot/lib/pkgconfig'
cmake_prefix_path = '$EMSDK/upstream/emscripten/cache/sysroot'
[properties]
needs_exe_wrapper = true
# <-- Replace with your real path to Emscripten.cmake:
cmake_toolchain_file = '/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake'
needs_exe_wrapper = true
cmake_system_name = 'Emscripten'
sys_root = '@env:EMSDK@/upstream/emscripten/cache/sysroot'

View File

@@ -0,0 +1,8 @@
var config = {
title: "Box2D Physics Demo",
width: 1280,
height: 720,
fullscreen: false
}
return config

343
examples/box2d_demo/main.js Normal file
View File

@@ -0,0 +1,343 @@
var box2d = use('box2d')
var moth = use('moth', $_.delay)
moth.initialize()
var draw2d = use('draw2d')
// Physics world setup
var world = new box2d.World({
gravity: {x: 0, y: -10}
})
// Ground body (static)
var ground = world.createBody({
type: 'static',
position: {x: 0, y: -10}
})
var groundShape = ground.createBoxShape({
width: 50,
height: 0.5,
density: 0,
friction: 0.7
})
// Walls
var leftWall = world.createBody({
type: 'static',
position: {x: -25, y: 0}
})
leftWall.createBoxShape({
width: 0.5,
height: 30,
density: 0
})
var rightWall = world.createBody({
type: 'static',
position: {x: 25, y: 0}
})
rightWall.createBoxShape({
width: 0.5,
height: 30,
density: 0
})
// Dynamic bodies array
var boxes = []
var circles = []
// Create some dynamic boxes
for (var i = 0; i < 5; i++) {
var box = world.createBody({
type: 'dynamic',
position: {x: -10 + i * 4, y: 5 + i * 3},
angle: Math.random() * Math.PI
})
box.createBoxShape({
width: 2,
height: 2,
density: 1.0,
friction: 0.3,
restitution: 0.5
})
boxes.push(box)
}
// Create some circles
for (var i = 0; i < 3; i++) {
var circle = world.createBody({
type: 'dynamic',
position: {x: 5 + i * 3, y: 10 + i * 2}
})
circle.createCircleShape({
radius: 1,
density: 0.5,
friction: 0.2,
restitution: 0.8
})
circles.push(circle)
}
// Connected bodies with distance joint
var bodyA = world.createBody({
type: 'dynamic',
position: {x: -5, y: 15}
})
bodyA.createCircleShape({
radius: 0.5,
density: 1.0
})
var bodyB = world.createBody({
type: 'dynamic',
position: {x: 0, y: 15}
})
bodyB.createCircleShape({
radius: 0.5,
density: 1.0
})
var joint = world.createDistanceJoint({
bodyA: bodyA,
bodyB: bodyB,
localAnchorA: {x: 0, y: 0},
localAnchorB: {x: 0, y: 0},
length: 5
})
// Mouse interaction
var mouseBody = null
var mousePressed = false
// Game state
var camera = {x: 0, y: 0, zoom: 10}
// Input state
var keys = {}
var mouse = {
pos: {x: 0, y: 0},
buttons: [false, false, false]
}
// Main update function
prosperon.on('update', function(dt) {
// Step physics simulation
world.step(dt, 4)
// Camera controls
if (keys[4]) camera.x -= 20 * dt // A
if (keys[7]) camera.x += 20 * dt // D
if (keys[26]) camera.y += 20 * dt // W
if (keys[22]) camera.y -= 20 * dt // S
if (keys[20]) camera.zoom *= 1 + dt // Q
if (keys[8]) camera.zoom *= 1 - dt // E
// Mouse interaction
var mouseWorld = screenToWorld(mouse.pos)
// Raycast to find body under mouse
var result = world.rayCast(mouseWorld, {x: 0, y: -1}, 0.1)
if (!result.hit) {
// Create new body at mouse position
if (keys[225]) { // LSHIFT
// Create circle
var newCircle = world.createBody({
type: 'dynamic',
position: mouseWorld
})
newCircle.createCircleShape({
radius: 0.5 + Math.random(),
density: 1.0,
restitution: 0.3 + Math.random() * 0.5
})
circles.push(newCircle)
} else {
// Create box
var newBox = world.createBody({
type: 'dynamic',
position: mouseWorld,
angle: Math.random() * Math.PI * 2
})
newBox.createBoxShape({
width: 1 + Math.random() * 2,
height: 1 + Math.random() * 2,
density: 1.0,
restitution: 0.2 + Math.random() * 0.3
})
boxes.push(newBox)
}
}
boxes.forEach(function(box) {
var dir = {
x: box.position.x - mouseWorld.x,
y: box.position.y - mouseWorld.y
}
var dist = Math.sqrt(dir.x * dir.x + dir.y * dir.y)
if (dist < 10 && dist > 0.1) {
dir.x /= dist
dir.y /= dist
box.applyLinearImpulse({x: dir.x * 50, y: dir.y * 50})
}
})
// Reset with R
if (keys[21] && !keys[21 + '_prev']) { // R key pressed
// Reset all dynamic bodies
boxes.forEach(function(box) {
box.position = {x: Math.random() * 20 - 10, y: 10 + Math.random() * 10}
box.angle = Math.random() * Math.PI * 2
box.linearVelocity = {x: 0, y: 0}
box.angularVelocity = 0
})
circles.forEach(function(circle) {
circle.position = {x: Math.random() * 20 - 10, y: 10 + Math.random() * 10}
circle.linearVelocity = {x: 0, y: 0}
})
}
// Update previous key states
for (var k in keys) {
if (k.indexOf('_prev') === -1) {
keys[k + '_prev'] = keys[k]
}
}
})
// Event handlers
prosperon.on('key_down', function(e) {
keys[e.scancode] = true
})
prosperon.on('key_up', function(e) {
keys[e.scancode] = false
})
prosperon.on('mouse_button_down', function(e) {
mouse.buttons[e.which] = true
if (e.which === 0 && !mousePressed) {
mousePressed = true
var mouseWorld = screenToWorld(mouse.pos)
// Raycast to find body under mouse
var result = world.rayCast(mouseWorld, {x: 0, y: -1}, 0.1)
if (!result.hit) {
// Create new body at mouse position
if (keys[225]) { // LSHIFT
// Create circle
var newCircle = world.createBody({
type: 'dynamic',
position: mouseWorld
})
newCircle.createCircleShape({
radius: 0.5 + Math.random(),
density: 1.0,
restitution: 0.3 + Math.random() * 0.5
})
circles.push(newCircle)
} else {
// Create box
var newBox = world.createBody({
type: 'dynamic',
position: mouseWorld,
angle: Math.random() * Math.PI * 2
})
newBox.createBoxShape({
width: 1 + Math.random() * 2,
height: 1 + Math.random() * 2,
density: 1.0,
restitution: 0.2 + Math.random() * 0.3
})
boxes.push(newBox)
}
}
}
if (e.which === 1) {
// Apply impulse with right click
var mouseWorld = screenToWorld(mouse.pos)
boxes.forEach(function(box) {
var dir = {
x: box.position.x - mouseWorld.x,
y: box.position.y - mouseWorld.y
}
var dist = Math.sqrt(dir.x * dir.x + dir.y * dir.y)
if (dist < 10 && dist > 0.1) {
dir.x /= dist
dir.y /= dist
box.applyLinearImpulse({x: dir.x * 50, y: dir.y * 50})
}
})
}
})
prosperon.on('mouse_button_up', function(e) {
mouse.buttons[e.which] = false
if (e.which === 0) {
mousePressed = false
}
})
prosperon.on('mouse_motion', function(e) {
mouse.pos = e.pos
})
// Rendering
prosperon.on('draw', function() {
// Clear background
// Draw ground
drawBox(ground.position, 50, 0.5, ground.angle, {r: 0.5, g: 0.5, b: 0.5, a: 1})
// Draw walls
drawBox(leftWall.position, 0.5, 30, 0, {r: 0.5, g: 0.5, b: 0.5, a: 1})
drawBox(rightWall.position, 0.5, 30, 0, {r: 0.5, g: 0.5, b: 0.5, a: 1})
// Draw boxes
boxes.forEach(function(box) {
drawBox(box.position, 2, 2, box.angle, {r: 0.8, g: 0.3, b: 0.3, a: 1})
})
// Draw circles
circles.forEach(function(circle) {
draw2d.circle(circle.position, 1, {r: 0.3, g: 0.8, b: 0.3, a: 1})
})
// Draw connected bodies
draw2d.circle(bodyA.position, 0.5, {r: 0.8, g: 0.8, b: 0.3, a: 1})
draw2d.circle(bodyB.position, 0.5, {r: 0.8, g: 0.8, b: 0.3, a: 1})
draw2d.line([bodyA.position, bodyB.position], {r: 1, g: 1, b: 0, a: 0.5})
// Draw UI
draw2d.text("Box2D Demo", {x: 10, y: 10}, 20, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("Controls:", {x: 10, y: 40}, 16, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("- WASD: Move camera", {x: 10, y: 60}, 14, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("- Q/E: Zoom in/out", {x: 10, y: 80}, 14, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("- Left click: Create box", {x: 10, y: 100}, 14, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("- Shift + Left click: Create circle", {x: 10, y: 120}, 14, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("- Right click: Apply impulse", {x: 10, y: 140}, 14, {r: 1, g: 1, b: 1, a: 1})
draw2d.text("- R: Reset bodies", {x: 10, y: 160}, 14, {r: 1, g: 1, b: 1, a: 1})
// Show physics stats
draw2d.text("Bodies: " + (boxes.length + circles.length + 4), {x: 10, y: 200}, 14, {r: 1, g: 1, b: 1, a: 1})
})
// Helper functions
function drawBox(pos, width, height, angle, color) {
draw2d.rectangle({x:pos.x,y:pos.y,width, height})
}
function screenToWorld(screenPos) {
return {
x: (screenPos.x - prosperon.x * 0.5) / camera.zoom + camera.x,
y: (screenPos.y - prosperon.y * 0.5) / camera.zoom + camera.y
}
}

View File

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -5,7 +5,6 @@ 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")
@@ -66,5 +65,5 @@ 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)
draw.text(msg, {x:0, y:0, width:config.width, height:40}, undefined, 0, Color.white, 0)
}

View File

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 398 B

View File

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 337 B

View File

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View File

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 379 B

57
examples/chess/grid.js Normal file
View File

@@ -0,0 +1,57 @@
var CELLS = Symbol()
var key = function key(x,y) { return `${x},${y}` }
function grid(w, h)
{
this[CELLS] = new Map()
this.width = w;
this.height = h;
}
grid.prototype = {
cell(x,y) {
var k = key(x,y)
if (!this[CELLS].has(k)) this[CELLS].set(k,[])
return this[CELLS].get(k)
},
add(entity, pos) {
this.cell(pos.x, pos.y).push(entity);
entity.coord = pos.slice();
},
remove(entity, pos) {
var c = this.cell(pos.x, pos.y);
c.splice(c.indexOf(entity), 1);
},
at(pos) {
return this.cell(pos.x, pos.y);
},
inBounds(pos) {
return pos.x >= 0 && pos.x < this.width && pos.y >= 0 && pos.y < this.height;
},
each(fn) {
for (var [k, list] of this[CELLS])
for (var p of list) fn(p, p.coord);
},
toString() {
var out = `grid [${this.width}x${this.height}]
`
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var cell = this.at([x,y]);
out += cell.length
}
if (y !== this.height - 1) out += "\n"
}
return out
},
}
return grid

View File

@@ -1,9 +1,16 @@
/* main.js runs the demo with your prototype-based grid */
var moth = use('moth', $_.delay)
var json = use('json')
var draw2d = use('prosperon/draw2d')
var blob = use('blob')
var res = 160
var internal_res = 480
moth.initialize({ width:res, height:res, resolution_x:internal_res, resolution_y:internal_res, mode:'letterbox' });
var os = use('os');
var draw2d = use('draw2d');
var gfx = use('graphics');
/*──── import our pieces + systems ───────────────────────────────────*/
var Grid = use('grid'); // your new ctor
@@ -41,14 +48,14 @@ function updateTitle() {
break;
case 'connected':
if (myColor) {
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
title += (mover.turn === myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
} else {
title += mover.turn + " turn";
}
break;
}
log.console(title)
prosperon.window.title = title
}
// Initialize title
@@ -63,11 +70,11 @@ var opponentMousePos = null;
var opponentHoldingPiece = false;
var opponentSelectPos = null;
function handleMouseButtonDown(e) {
if (e.which != 0) return;
prosperon.on('mouse_button_down', function(e) {
if (e.which !== 0) return;
// Don't allow piece selection unless we have an opponent
if (gameState != 'connected' || !opponent) return;
if (gameState !== 'connected' || !opponent) return;
var mx = e.mouse.x;
var my = e.mouse.y;
@@ -76,7 +83,7 @@ function handleMouseButtonDown(e) {
if (!grid.inBounds(c)) return;
var cell = grid.at(c);
if (cell.length && cell[0].colour == mover.turn) {
if (cell.length && cell[0].colour === mover.turn) {
selectPos = c;
holdingPiece = true;
// Send pickup notification to opponent
@@ -89,13 +96,13 @@ function handleMouseButtonDown(e) {
} else {
selectPos = null;
}
}
})
function handleMouseButtonUp(e) {
if (e.which != 0 || !holdingPiece || !selectPos) return;
prosperon.on('mouse_button_up', function(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) {
if (gameState !== 'connected' || !opponent || !isMyTurn) {
holdingPiece = false;
return;
}
@@ -110,16 +117,16 @@ function handleMouseButtonUp(e) {
}
if (mover.tryMove(grid.at(selectPos)[0], c)) {
log.console("Made move from", selectPos, "to", c);
console.log("Made move from", selectPos, "to", c);
// Send move to opponent
log.console("Sending move to opponent:", opponent);
console.log("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");
console.log("Move sent, now opponent's turn");
selectPos = null;
updateTitle();
}
@@ -132,9 +139,9 @@ function handleMouseButtonUp(e) {
type: 'piece_drop'
});
}
}
})
function handleMouseMotion(e) {
prosperon.on('mouse_motion', function(e) {
var mx = e.pos.x;
var my = e.pos.y;
@@ -147,7 +154,7 @@ function handleMouseMotion(e) {
hoverPos = c;
// Send mouse position to opponent in real-time
if (opponent && gameState == 'connected') {
if (opponent && gameState === 'connected') {
send(opponent, {
type: 'mouse_move',
pos: c,
@@ -155,18 +162,7 @@ function handleMouseMotion(e) {
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 ─────────────────────────────────────────────────── */
@@ -181,8 +177,8 @@ var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
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 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;
@@ -197,8 +193,7 @@ function drawBoard() {
draw2d.rectangle(
{ x: x*S, y: y*S, width: S, height: S },
{ thickness: 0 },
{ color: color }
{ thickness: 0, color: color }
);
}
}
@@ -211,7 +206,7 @@ function isValidMoveForTurn(from, to) {
// Check if the destination has a piece of the same color
var destCell = grid.at(to);
if (destCell.length && destCell[0].colour == piece.colour) {
if (destCell.length && destCell[0].colour === piece.colour) {
return false;
}
@@ -225,22 +220,22 @@ function drawPieces() {
// Skip drawing the piece being held (by me or opponent)
if (holdingPiece && selectPos &&
piece.coord[0] == selectPos[0] &&
piece.coord[1] == selectPos[1]) {
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]) {
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);
draw2d.image(piece.sprite, r, 0, [0,0], [0,0], {mode:"nearest"});
});
// Draw the held piece at the mouse position if we're holding one
@@ -250,7 +245,7 @@ function drawPieces() {
var r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
width:S, height:S };
draw2d.image(piece.sprite, r);
draw2d.image(piece.sprite, r, 0, [0,0], [0,0], {mode:"nearest"});
}
}
@@ -262,23 +257,29 @@ function drawPieces() {
width:S, height:S };
// Draw with slight transparency to show it's the opponent's piece
draw2d.image(opponentPiece.sprite, r);
draw2d.image(opponentPiece.sprite, r, 0, [0,0], [0,0], {mode:"nearest", color: [1, 1, 1, 0.7]});
}
}
}
function update(dt)
{
return {}
}
var graphics = use('graphics')
function draw()
{
draw2d.clear()
prosperon.on('draw', function() {
drawBoard()
drawPieces()
return draw2d.get_commands()
}
draw2d.text("HELL", [100,100])
})
prosperon.on('key_down', function(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();
}
})
function startServer() {
gameState = 'server_waiting';
@@ -288,11 +289,11 @@ function startServer() {
updateTitle();
$_.portal(e => {
log.console("Portal received contact message");
console.log("Portal received contact message");
// Reply with this actor to establish connection
log.console (json.encode($_))
console.log (json.encode($_))
send(e, $_);
log.console("Portal replied with server actor");
console.log("Portal replied with server actor");
}, 5678);
}
@@ -301,10 +302,10 @@ function joinServer() {
updateTitle();
function contact_fn(actor, reason) {
log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
console.log("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
if (actor) {
opponent = actor;
log.console("Connection established with server, sending join request");
console.log("Connection established with server, sending join request");
// Send a greet message with our actor object
send(opponent, {
@@ -312,7 +313,7 @@ function joinServer() {
client_actor: $_
});
} else {
log.console(`Failed to connect: ${json.encode(reason)}`);
console.log(`Failed to connect: ${json.encode(reason)}`);
gameState = 'waiting';
updateTitle();
}
@@ -324,38 +325,51 @@ function joinServer() {
});
}
var os = use('os')
var actor = use('actor')
for (var i in actor) console.log(i)
// Set up IO actor subscription
var ioguy = {
__ACTORDATA__: {
id: actor.ioactor()
}
};
send(ioguy, {
type: "subscribe",
actor: $_
});
$_.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 === 'game_start' || e.type === 'move' || e.type === 'greet')
console.log("Receiver got message:", e.type, e);
if (e.type === 'quit') os.exit()
if (e.type == 'greet') {
log.console("Server received greet from client");
if (e.type === 'greet') {
console.log("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));
console.log("Stored client actor:", json.encode(opponent));
gameState = 'connected';
updateTitle();
// Send game_start to the client
log.console("Sending game_start to client");
console.log("Sending game_start to client");
send(opponent, {
type: 'game_start',
your_color: 'black'
});
log.console("game_start message sent to client");
console.log("game_start message sent to client");
}
else if (e.type == 'game_start') {
log.console("Game starting, I am:", e.your_color);
else if (e.type === 'game_start') {
console.log("Game starting, I am:", e.your_color);
myColor = e.your_color;
isMyTurn = (myColor == 'white');
isMyTurn = (myColor === 'white');
gameState = 'connected';
updateTitle();
} else if (e.type == 'move') {
log.console("Received move from opponent:", e.from, "to", e.to);
} else if (e.type === 'move') {
console.log("Received move from opponent:", e.from, "to", e.to);
// Apply opponent's move
var fromCell = grid.at(e.from);
if (fromCell.length) {
@@ -363,33 +377,27 @@ $_.receiver(e => {
if (mover.tryMove(piece, e.to)) {
isMyTurn = true; // It's now our turn
updateTitle();
log.console("Applied opponent move, now my turn");
console.log("Applied opponent move, now my turn");
} else {
log.console("Failed to apply opponent move");
console.log("Failed to apply opponent move");
}
} else {
log.console("No piece found at from position");
console.log("No piece found at from position");
}
} else if (e.type == 'mouse_move') {
} 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') {
} else if (e.type === 'piece_pickup') {
// Opponent picked up a piece
opponentSelectPos = e.pos;
opponentHoldingPiece = true;
} else if (e.type == 'piece_drop') {
} 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)
}
})
prosperon.dispatch(e.type, e)
})

View File

@@ -5,17 +5,17 @@ var MovementSystem = function(grid, rules) {
}
MovementSystem.prototype.tryMove = function (piece, to) {
if (piece.colour != this.turn) return false;
if (piece.colour !== this.turn) return false;
// normalise to into our hybrid coord
var dest = [to.x ?? t[0],
to.y ?? to[1]];
var dest = [to.x !== undefined ? to.x : to[0],
to.y !== undefined ? 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].colour === piece.colour) return false;
if (victims.length) victims[0].captured = true;
this.grid.remove(piece, piece.coord);
@@ -25,7 +25,7 @@ MovementSystem.prototype.tryMove = function (piece, to) {
piece.coord.x = dest.x;
piece.coord.y = dest.y;
this.turn = (this.turn == 'white') ? 'black' : 'white';
this.turn = (this.turn === 'white') ? 'black' : 'white';
return true;
};

45
examples/chess/rules.js Normal file
View File

@@ -0,0 +1,45 @@
/* helper robust coord access */
function cx(c) { return (c.x !== undefined) ? c.x : c[0]; }
function cy(c) { return (c.y !== undefined) ? 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 };

View File

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View File

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

View File

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 381 B

View File

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 313 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View File

@@ -18,7 +18,7 @@ var state = {
// Helper to calculate progress percentage
function get_progress() {
if (state.total_bytes == 0) {
if (state.total_bytes === 0) {
return 0;
}
return Math.round((state.downloaded_bytes / state.total_bytes) * 100);
@@ -101,7 +101,7 @@ $_.receiver(function(msg) {
break;
case 'status':
log.console(`got status request. current is ${json.encode(get_status())}`)
console.log(`got status request. current is ${json.encode(get_status())}`)
send(msg, {
type: 'status_response',
...get_status()
@@ -150,7 +150,7 @@ function read_next_chunk() {
try {
var chunk = http.fetch_read_chunk(state.connection);
if (chunk == null) {
if (chunk === null) {
// Download complete
finish_download();
return;

View File

@@ -8,22 +8,22 @@ var waiting_client = null;
var match_id = 0;
$_.portal(e => {
log.console("NAT server: received connection request");
console.log("NAT server: received connection request");
if (!is_actor(e.actor))
send(e, {reason: "Must provide the actor you want to connect."});
if (waiting_client) {
log.console(`sending out messages! to ${json.encode(e.actor)} and ${json.encode(waiting_client.actor)}`)
console.log(`sending out messages! to ${json.encode(e.actor)} and ${json.encode(waiting_client.actor)}`)
send(waiting_client, e.actor)
send(e, waiting_client.actor)
waiting_client = null
waiting_client = undefined
return
}
waiting_client = e
log.console(`actor ${json.encode(e.actor)} is waiting ...`)
console.log(`actor ${json.encode(e.actor)} is waiting ...`)
}, 4000);

View File

@@ -1,11 +1,11 @@
log.console(`nat client starting`)
console.log(`nat client starting`)
$_.contact((actor, reason) => {
if (actor) {
log.console(`trying to message ${json.encode(actor)}`)
console.log(`trying to message ${json.encode(actor)}`)
send(actor, {type:"greet"})
} else {
log.console(json.encode(reason))
console.log(json.encode(reason))
}
}, {
address: "108.210.60.32", // NAT server's public IP
@@ -16,7 +16,7 @@ $_.contact((actor, reason) => {
$_.receiver(e => {
switch(e.type) {
case 'greet':
log.console(`hello!`)
console.log(`hello!`)
break
}
})

View File

@@ -2,7 +2,6 @@
var draw = use('draw2d')
var input = use('controller')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
@@ -74,13 +73,13 @@ this.hud = function() {
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.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)
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)
draw.text(msg, {x:0, y:10, width:config.width, height:40}, undefined, 0, Color.white, 0)
}

View File

@@ -4,7 +4,6 @@ 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]
@@ -36,7 +35,7 @@ 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 }
if (snake[i].x === apple.x && snake[i].y === apple.y) { spawnApple(); return }
}
function wrap(pos) {
@@ -49,7 +48,7 @@ function wrap(pos) {
resetGame()
this.update = function(dt) {
if (gameState != "playing") return
if (gameState !== "playing") return
moveTimer += dt
if (moveTimer < moveInterval) return
moveTimer -= moveInterval
@@ -63,7 +62,7 @@ this.update = function(dt) {
// Check collision with body
for (var i=0; i<snake.length; i++) {
if (snake[i].x == head.x && snake[i].y == head.y) {
if (snake[i].x === head.x && snake[i].y === head.y) {
gameState = "gameover"
return
}
@@ -73,7 +72,7 @@ this.update = function(dt) {
snake.unshift(head)
// Eat apple?
if (head.x == apple.x && head.y == apple.y) spawnApple()
if (head.x === apple.x && head.y === apple.y) spawnApple()
else snake.pop()
}
@@ -84,15 +83,15 @@ this.hud = function() {
// 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.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)
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, Color.red)
if (gameState == "gameover") {
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)
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, undefined, 0, Color.white)
}
}
@@ -100,19 +99,19 @@ this.hud = function() {
// "Up" means y=1, so going physically up on screen
this.inputs = {
up: function() {
if (direction.y != -1) nextDirection = {x:0,y:1}
if (direction.y !== -1) nextDirection = {x:0,y:1}
},
down: function() {
if (direction.y != 1) nextDirection = {x:0,y:-1}
if (direction.y !== 1) nextDirection = {x:0,y:-1}
},
left: function() {
if (direction.x != 1) nextDirection = {x:-1,y:0}
if (direction.x !== 1) nextDirection = {x:-1,y:0}
},
right: function() {
if (direction.x != -1) nextDirection = {x:1,y:0}
if (direction.x !== -1) nextDirection = {x:1,y:0}
},
space: function() {
if (gameState=="gameover") resetGame()
if (gameState==="gameover") resetGame()
}
}

View File

@@ -23,23 +23,23 @@ var stats_loaded = false;
// Initialize Steam
function init_steam() {
if (!steam) {
log.console("Steam module not available");
console.log("Steam module not available");
return false;
}
log.console("Initializing Steam...");
console.log("Initializing Steam...");
steam_available = steam.steam_init();
if (steam_available) {
log.console("Steam initialized successfully");
console.log("Steam initialized successfully");
// Request current stats/achievements
if (steam.stats.stats_request()) {
log.console("Stats requested");
console.log("Stats requested");
stats_loaded = true;
}
} else {
log.console("Failed to initialize Steam");
console.log("Failed to initialize Steam");
}
return steam_available;
@@ -59,13 +59,13 @@ function unlock_achievement(achievement_name) {
// Check if already unlocked
var unlocked = steam.achievement.achievement_get(achievement_name);
if (unlocked) {
log.console("Achievement already unlocked:", achievement_name);
console.log("Achievement already unlocked:", achievement_name);
return true;
}
// Unlock it
if (steam.achievement.achievement_set(achievement_name)) {
log.console("Achievement unlocked:", achievement_name);
console.log("Achievement unlocked:", achievement_name);
// Store stats to make it permanent
steam.stats.stats_store();
@@ -87,7 +87,7 @@ function update_stat(stat_name, value, is_float) {
}
if (success) {
log.console("Stat updated:", stat_name, "=", value);
console.log("Stat updated:", stat_name, "=", value);
steam.stats.stats_store();
}
@@ -115,7 +115,7 @@ function start_game() {
total_score = get_stat(STATS.TOTAL_SCORE, false);
current_score = 0;
log.console("Starting game #" + (games_played + 1));
console.log("Starting game #" + (games_played + 1));
}
function end_game(score) {
@@ -128,7 +128,7 @@ function end_game(score) {
update_stat(STATS.TOTAL_SCORE, total_score, false);
// Check for achievements
if (games_played == 1) {
if (games_played === 1) {
unlock_achievement(ACHIEVEMENTS.FIRST_WIN);
}
@@ -167,7 +167,7 @@ function load_from_cloud() {
function cleanup_steam() {
if (steam_available) {
steam.steam_shutdown();
log.console("Steam shut down");
console.log("Steam shut down");
}
}

View File

@@ -1,7 +1,6 @@
var draw = use('draw2d')
var input = use('input')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
@@ -128,10 +127,10 @@ function clearLines() {
}
}
// Score
if (lines==1) score += 100
else if (lines==2) score += 300
else if (lines==3) score += 500
else if (lines==4) score += 800
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)
}
@@ -153,7 +152,7 @@ spawnPiece()
this.update = function(dt) {
if (gameOver) return
// ======= Horizontal Movement Gate =======
// ========== Horizontal Movement Gate ==========
var leftPressed = input.keyboard.down('a')
var rightPressed = input.keyboard.down('d')
var horizontalMove = 0
@@ -191,7 +190,7 @@ this.update = function(dt) {
hMoveTimer -= dt
prevLeft = leftPressed
prevRight = rightPressed
// ======= End Horizontal Movement Gate =======
// ========== End Horizontal Movement Gate ==========
// Rotate with W (once per press, no spinning)
if (input.keyboard.down('w')) {
@@ -249,7 +248,7 @@ this.hud = function() {
}
// Next piece window
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
draw.text("Next", {x:70, y:5, width:50, height:10}, undefined, 0, Color.white)
if (nextPiece) {
for (var i=0; i<nextPiece.blocks.length; i++) {
var nx = nextPiece.blocks[i][0]
@@ -262,10 +261,10 @@ this.hud = function() {
// 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)
draw.text(info, {x:70, y:30, width:90, height:50}, undefined, 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)
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, undefined, 0, Color.red)
}
}

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 867 B

After

Width:  |  Height:  |  Size: 867 B

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

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