fix examples
This commit is contained in:
86
camera.cm
86
camera.cm
@@ -1,7 +1,9 @@
|
||||
var backend = use('sdl_gpu')
|
||||
|
||||
var cam = {}
|
||||
|
||||
/*
|
||||
presentation can be one of
|
||||
/*
|
||||
presentation can be one of
|
||||
letterbox
|
||||
overscan
|
||||
stretch
|
||||
@@ -13,6 +15,27 @@ function rect_contains_pt(rect, x, y) {
|
||||
y >= rect.y && y <= rect.y + rect.height
|
||||
}
|
||||
|
||||
function place_rect(src, dst, mode) {
|
||||
if (mode == 'stretch')
|
||||
return {x: 0, y: 0, width: dst.width, height: dst.height}
|
||||
|
||||
var sx = 0, sy = 0, s = 0, w = 0, h = 0, scale = 0
|
||||
if (mode == 'integer_scale') {
|
||||
sx = floor(dst.width / src.width)
|
||||
sy = floor(dst.height / src.height)
|
||||
s = max(1, min(sx, sy))
|
||||
w = src.width * s
|
||||
h = src.height * s
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
// letterbox (default)
|
||||
scale = min(dst.width / src.width, dst.height / src.height)
|
||||
w = src.width * scale
|
||||
h = src.height * scale
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
var basecam = {
|
||||
pos: {x:0,y:0},
|
||||
ortho:true,
|
||||
@@ -30,36 +53,32 @@ var basecam = {
|
||||
return this.y/this.x;
|
||||
},
|
||||
|
||||
screen_rect() {
|
||||
var src = { width: this.width, height: this.height }
|
||||
var dst = { x: 0, y: 0, width: gameres.width, height: gameres.height }
|
||||
return place_rect(src, dst, this.presentation, this.align_x, this.align_y, false)
|
||||
},
|
||||
|
||||
rect() {
|
||||
return {x:0, y:0, width: this.width, height: this.height}
|
||||
},
|
||||
|
||||
|
||||
sensor() {
|
||||
var ws = backend.get_window_size()
|
||||
return {
|
||||
width: gameres.width,
|
||||
height: gameres.height,
|
||||
width: ws.width,
|
||||
height: ws.height,
|
||||
}
|
||||
},
|
||||
|
||||
// The rectangle (in window pixels) where this camera’s target will be drawn
|
||||
// The rectangle (in window pixels) where this camera’s target will be drawn
|
||||
screen_rect() {
|
||||
var src = { width: this.width, height: this.height }
|
||||
var dst = { x: 0, y: 0, width: gameres.width, height: gameres.height }
|
||||
return place_rect(src, dst, this.presentation, this.align_x, this.align_y, false)
|
||||
var ws = backend.get_window_size()
|
||||
var dst = { x: 0, y: 0, width: ws.width, height: ws.height }
|
||||
return place_rect(src, dst, this.presentation)
|
||||
},
|
||||
|
||||
// --- Space converters ---------------------------------------------------
|
||||
// World -> View UV [0..1] independent of pixels/letterbox
|
||||
world_to_view_uv(wx, wy) {
|
||||
var ax = this.anchor[0], ay = this.anchor[1]
|
||||
var u = (wx - this.pos[0]) / this.width + ax
|
||||
var v = (wy - this.pos[1]) / this.height + ay
|
||||
var ax = this.anchor.x, ay = this.anchor.y
|
||||
var u = (wx - this.pos.x) / this.width + ax
|
||||
var v = (wy - this.pos.y) / this.height + ay
|
||||
|
||||
// apply viewport (maps camera-local UV into sub-rect)
|
||||
var vp = this.viewport
|
||||
@@ -73,9 +92,9 @@ var basecam = {
|
||||
var vp = this.viewport
|
||||
var uu = (u - vp.x) / vp.width
|
||||
var vv = (v - vp.y) / vp.height
|
||||
var ax = this.anchor[0], ay = this.anchor[1]
|
||||
var wx = this.pos[0] + (uu - ax) * this.width
|
||||
var wy = this.pos[1] + (vv - ay) * this.height
|
||||
var ax = this.anchor.x, ay = this.anchor.y
|
||||
var wx = this.pos.x + (uu - ax) * this.width
|
||||
var wy = this.pos.y + (vv - ay) * this.height
|
||||
return { x: wx, y: wy }
|
||||
},
|
||||
|
||||
@@ -91,39 +110,38 @@ var basecam = {
|
||||
// Window pixels -> World (mouse picking)
|
||||
window_to_world(sx, sy) {
|
||||
var sr = this.screen_rect()
|
||||
if (!rect_contains_pt(sr, sx, sy)) {
|
||||
// outside letterbox bars; clamp or return null
|
||||
// return null
|
||||
}
|
||||
var u = (sx - sr.x) / sr.width
|
||||
var v = (sy - sr.y) / sr.height
|
||||
var v = 1 - (sy - sr.y) / sr.height
|
||||
return this.view_uv_to_world(u, v)
|
||||
},
|
||||
|
||||
// World -> normalized window [0..1]
|
||||
world_to_screen_norm(wx, wy) {
|
||||
var p = this.world_to_window(wx, wy)
|
||||
return { x: p.x / gameres.width, y: p.y / gameres.height }
|
||||
var ws = backend.get_window_size()
|
||||
return { x: p.x / ws.width, y: p.y / ws.height }
|
||||
},
|
||||
|
||||
// Normalized window [0..1] -> World
|
||||
screen_norm_to_world(nx, ny) {
|
||||
var sx = nx * gameres.width
|
||||
var sy = ny * gameres.height
|
||||
var ws = backend.get_window_size()
|
||||
var sx = nx * ws.width
|
||||
var sy = ny * ws.height
|
||||
return this.window_to_world(sx, sy)
|
||||
},
|
||||
|
||||
screen_to_world: function(sx, sy) {
|
||||
// sx, sy are normalized screen coordinates (0-1), bottom left [0,0], top right [1,1]
|
||||
var screen_rect = this.screen_rect()
|
||||
var pixel_x = sx * gameres.width
|
||||
var pixel_y = sy * gameres.height
|
||||
var ws = backend.get_window_size()
|
||||
var pixel_x = sx * ws.width
|
||||
var pixel_y = sy * ws.height
|
||||
var rel_x = (pixel_x - screen_rect.x) / screen_rect.width
|
||||
var rel_y = (pixel_y - screen_rect.y) / screen_rect.height
|
||||
var ax = this.anchor[0]
|
||||
var ay = this.anchor[1]
|
||||
var world_x = this.pos[0] + (rel_x - ax) * this.width
|
||||
var world_y = this.pos[1] + (rel_y - ay) * this.height
|
||||
var ax = this.anchor.x
|
||||
var ay = this.anchor.y
|
||||
var world_x = this.pos.x + (rel_x - ax) * this.width
|
||||
var world_y = this.pos.y + (rel_y - ay) * this.height
|
||||
return {x: world_x, y: world_y}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -123,9 +123,8 @@ function compile_plane(plane_config, ctx, group_effects) {
|
||||
// Allocate plane target
|
||||
var plane_target = ctx.alloc(res.width, res.height, plane_config.name)
|
||||
|
||||
// Clear plane
|
||||
if (plane_config.clear)
|
||||
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear})
|
||||
// Always clear plane target to prevent stale data between frames
|
||||
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear || {r: 0, g: 0, b: 0, a: 0}})
|
||||
|
||||
// Render each effect group to temp target, apply effects, composite back
|
||||
arrfor(array(effect_groups), gname => {
|
||||
|
||||
@@ -59,7 +59,7 @@ for (i = 0; i < 100; i++) {
|
||||
random.random() * GH
|
||||
)
|
||||
}
|
||||
count_label.text = "Bunnies: " + length(bunnies)
|
||||
count_label.text = "Bunnies: " + text(length(bunnies))
|
||||
|
||||
// Mouse position tracking
|
||||
var mouse_x = GW / 2, mouse_y = GH / 2
|
||||
@@ -85,12 +85,12 @@ core.start({
|
||||
update: function(dt) {
|
||||
// Spawn bunnies while clicking
|
||||
var down = input.player1().down()
|
||||
var si = 0
|
||||
if (down.spawn) {
|
||||
var si = 0
|
||||
for (si = 0; si < 50; si++) {
|
||||
add_bunny(mouse_x, mouse_y)
|
||||
}
|
||||
count_label.text = "Bunnies: " + length(bunnies)
|
||||
count_label.text = "Bunnies: " + text(length(bunnies))
|
||||
}
|
||||
|
||||
// Update all bunnies
|
||||
@@ -112,7 +112,7 @@ core.start({
|
||||
frame_count++
|
||||
fps_timer += dt
|
||||
if (fps_timer >= 1) {
|
||||
fps_label.text = "FPS: " + frame_count
|
||||
fps_label.text = "FPS: " + text(frame_count)
|
||||
frame_count = 0
|
||||
fps_timer -= 1
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@ var camera = use('camera')
|
||||
var compositor = use('compositor')
|
||||
var input = use('input')
|
||||
var shape = use('shape2d')
|
||||
var sprite_factory = use('sprite')
|
||||
var text2d = use('text2d')
|
||||
|
||||
var Grid = use('grid')
|
||||
var MovementSystem = use('movement').MovementSystem
|
||||
var startingPos = use('pieces').startingPosition
|
||||
var rules = use('rules')
|
||||
var Grid = use('examples/chess/grid')
|
||||
var MovementSystem = use('examples/chess/movement')
|
||||
var startingPos = use('examples/chess/pieces').startingPosition
|
||||
var rules = use('examples/chess/rules')
|
||||
|
||||
var S = 60
|
||||
var GW = S * 8, GH = S * 8
|
||||
var move_history = []
|
||||
|
||||
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||
@@ -38,11 +38,11 @@ var select_color = {r: 1, g: 0.84, b: 0, a: 1}
|
||||
var valid_color = {r: 0.6, g: 0.8, b: 0.4, a: 1}
|
||||
|
||||
var board_shapes = []
|
||||
var bx = 0, by = 0
|
||||
var bx = 0, by = 0, row = null, col = null
|
||||
for (by = 0; by < 8; by++) {
|
||||
var row = []
|
||||
row = []
|
||||
for (bx = 0; bx < 8; bx++) {
|
||||
var col = ((bx + by) & 1) ? dark_color : light_color
|
||||
col = ((bx + by) & 1) ? dark_color : light_color
|
||||
push(row, shape.rect({
|
||||
pos: {x: bx * S + S / 2, y: by * S + S / 2},
|
||||
width: S, height: S,
|
||||
@@ -53,17 +53,25 @@ for (by = 0; by < 8; by++) {
|
||||
push(board_shapes, row)
|
||||
}
|
||||
|
||||
// Piece sprites — one per piece, keyed by piece object
|
||||
var piece_sprites = {}
|
||||
// Piece letter abbreviations
|
||||
var piece_letter = {
|
||||
king: "K", queen: "Q", rook: "R",
|
||||
bishop: "B", knight: "N", pawn: "P"
|
||||
}
|
||||
var white_color = {r: 1, g: 1, b: 1, a: 1}
|
||||
var black_color = {r: 0.1, g: 0.1, b: 0.1, a: 1}
|
||||
|
||||
// Piece labels — one per piece, keyed by piece id
|
||||
var piece_labels = {}
|
||||
var piece_id = 0
|
||||
grid.each(function(p) {
|
||||
piece_id++
|
||||
p._id = piece_id
|
||||
piece_sprites[piece_id] = sprite_factory({
|
||||
image: p.sprite,
|
||||
piece_labels[text(piece_id)] = text2d({
|
||||
text: piece_letter[p.kind],
|
||||
pos: {x: p.coord[0] * S + S / 2, y: p.coord[1] * S + S / 2},
|
||||
width: S, height: S,
|
||||
anchor_x: 0.5, anchor_y: 0.5,
|
||||
size: 36,
|
||||
color: (p.colour == 'white') ? white_color : black_color,
|
||||
plane: 'game', layer: 1
|
||||
})
|
||||
})
|
||||
@@ -86,17 +94,27 @@ function update_status() {
|
||||
}
|
||||
|
||||
function reset_board_colors() {
|
||||
var x = 0, y = 0
|
||||
var x = 0, y = 0, col = null
|
||||
for (y = 0; y < 8; y++) {
|
||||
for (x = 0; x < 8; x++) {
|
||||
var col = ((x + y) & 1) ? dark_color : light_color
|
||||
col = ((x + y) & 1) ? dark_color : light_color
|
||||
board_shapes[y][x].fill = {r: col.r, g: col.g, b: col.b, a: col.a}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hover_color = {r: 0.8, g: 0.85, b: 0.6, a: 1}
|
||||
|
||||
function highlight_selection() {
|
||||
reset_board_colors()
|
||||
|
||||
// Hover highlight
|
||||
if (hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
|
||||
board_shapes[hover_gy][hover_gx].fill = {
|
||||
r: hover_color.r, g: hover_color.g, b: hover_color.b, a: hover_color.a
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectPos) return
|
||||
|
||||
// Highlight selected square
|
||||
@@ -105,9 +123,9 @@ function highlight_selection() {
|
||||
}
|
||||
|
||||
// Highlight valid moves
|
||||
var i = 0
|
||||
var i = 0, m = null
|
||||
for (i = 0; i < length(validMoves); i++) {
|
||||
var m = validMoves[i]
|
||||
m = validMoves[i]
|
||||
board_shapes[m[1]][m[0]].fill = {
|
||||
r: valid_color.r, g: valid_color.g, b: valid_color.b, a: valid_color.a
|
||||
}
|
||||
@@ -132,22 +150,24 @@ function compute_valid_moves(from) {
|
||||
}
|
||||
|
||||
function sync_piece_sprites() {
|
||||
var keys = array(piece_labels)
|
||||
var i = 0
|
||||
for (i = 0; i < length(keys); i++) {
|
||||
piece_labels[keys[i]].visible = false
|
||||
}
|
||||
grid.each(function(p) {
|
||||
var spr = piece_sprites[p._id]
|
||||
if (!spr) return
|
||||
if (p.captured) {
|
||||
spr.visible = false
|
||||
} else {
|
||||
spr.pos.x = p.coord[0] * S + S / 2
|
||||
spr.pos.y = p.coord[1] * S + S / 2
|
||||
spr.visible = true
|
||||
}
|
||||
var lbl = piece_labels[text(p._id)]
|
||||
if (!lbl) return
|
||||
lbl.pos.x = p.coord[0] * S + S / 2
|
||||
lbl.pos.y = p.coord[1] * S + S / 2
|
||||
lbl.visible = true
|
||||
})
|
||||
}
|
||||
|
||||
// Input handler
|
||||
var game_input = {
|
||||
on_input: function(action, data) {
|
||||
var clicked = null, cell = null, is_valid = false, i = 0, src_piece = null
|
||||
if (!data.pressed) return
|
||||
|
||||
if (action == 'cancel') {
|
||||
@@ -158,13 +178,13 @@ var game_input = {
|
||||
}
|
||||
|
||||
if (action == 'select' && hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
|
||||
var clicked = [hover_gx, hover_gy]
|
||||
var cell = grid.at(clicked)
|
||||
clicked = [hover_gx, hover_gy]
|
||||
cell = grid.at(clicked)
|
||||
|
||||
if (selectPos) {
|
||||
// Try to move
|
||||
var is_valid = false
|
||||
var i = 0
|
||||
is_valid = false
|
||||
i = 0
|
||||
for (i = 0; i < length(validMoves); i++) {
|
||||
if (validMoves[i][0] == clicked[0] && validMoves[i][1] == clicked[1]) {
|
||||
is_valid = true
|
||||
@@ -173,8 +193,10 @@ var game_input = {
|
||||
}
|
||||
|
||||
if (is_valid) {
|
||||
var src_piece = grid.at(selectPos)[0]
|
||||
src_piece = grid.at(selectPos)[0]
|
||||
if (src_piece && mover.tryMove(src_piece, clicked)) {
|
||||
move_history[] = sq_name(selectPos[0], selectPos[1]) + "-" + sq_name(clicked[0], clicked[1])
|
||||
log.chess("move " + sq_name(selectPos[0], selectPos[1]) + "-" + sq_name(clicked[0], clicked[1]) + " turn=" + mover.turn)
|
||||
sync_piece_sprites()
|
||||
update_status()
|
||||
}
|
||||
@@ -201,6 +223,66 @@ var game_input = {
|
||||
}
|
||||
input.player1().possess(game_input)
|
||||
|
||||
// --- Probe endpoints for AI play ---
|
||||
var probe = use('probe')
|
||||
|
||||
var file_letters = "abcdefgh"
|
||||
function sq_name(x, y) {
|
||||
return text(file_letters, x, x + 1) + text(y + 1)
|
||||
}
|
||||
|
||||
function piece_char(p) {
|
||||
var chars = {king: "K", queen: "Q", rook: "R", bishop: "B", knight: "N", pawn: "P"}
|
||||
var c = chars[p.kind]
|
||||
if (p.colour == 'black') c = lower(c)
|
||||
return c
|
||||
}
|
||||
|
||||
probe.register("chess", {
|
||||
board: function(a) {
|
||||
var rows = []
|
||||
var y = 0, x = 0, cell = null, row = null
|
||||
for (y = 7; y >= 0; y--) {
|
||||
row = ""
|
||||
for (x = 0; x < 8; x++) {
|
||||
cell = grid.at([x, y])
|
||||
if (length(cell)) {
|
||||
row = row + piece_char(cell[0])
|
||||
} else {
|
||||
row = row + "."
|
||||
}
|
||||
if (x < 7) row = row + " "
|
||||
}
|
||||
rows[] = row
|
||||
}
|
||||
return {turn: mover.turn, moves: move_history, board: rows}
|
||||
},
|
||||
|
||||
move: function(a) {
|
||||
var fx = a.fx, fy = a.fy, tx = a.tx, ty = a.ty
|
||||
if (is_null(fx) || is_null(fy) || is_null(tx) || is_null(ty))
|
||||
return {ok: false, error: "need fx fy tx ty"}
|
||||
var from = [fx, fy]
|
||||
var to = [tx, ty]
|
||||
var cell = grid.at(from)
|
||||
if (!length(cell))
|
||||
return {ok: false, error: "no piece at " + sq_name(fx, fy)}
|
||||
var piece = cell[0]
|
||||
if (piece.colour != mover.turn)
|
||||
return {ok: false, error: "not " + piece.colour + "'s turn"}
|
||||
if (!rules.canMove(piece, from, to, grid))
|
||||
return {ok: false, error: "illegal move"}
|
||||
if (mover.tryMove(piece, to)) {
|
||||
move_history[] = sq_name(fx, fy) + "-" + sq_name(tx, ty)
|
||||
log.chess("move " + sq_name(fx, fy) + "-" + sq_name(tx, ty) + " turn=" + mover.turn)
|
||||
sync_piece_sprites()
|
||||
update_status()
|
||||
return {ok: true, move: sq_name(fx, fy) + "-" + sq_name(tx, ty)}
|
||||
}
|
||||
return {ok: false, error: "move rejected"}
|
||||
}
|
||||
})
|
||||
|
||||
var comp_config = {
|
||||
clear: {r: 0.15, g: 0.15, b: 0.2, a: 1},
|
||||
planes: [
|
||||
@@ -210,12 +292,13 @@ var comp_config = {
|
||||
}
|
||||
|
||||
core.start({
|
||||
width: 640, height: 640, title: "Chess",
|
||||
width: 640, height: 640, title: "Chess", probe: true,
|
||||
|
||||
input: function(ev) {
|
||||
var wp = null
|
||||
if (ev.type == 'mouse_motion') {
|
||||
// Convert pixel coords to grid coords via camera
|
||||
var wp = game_cam.window_to_world(ev.pos.x, ev.pos.y)
|
||||
wp = game_cam.window_to_world(ev.pos[0], ev.pos[1])
|
||||
if (wp) {
|
||||
hover_gx = floor(wp.x / S)
|
||||
hover_gy = floor(wp.y / S)
|
||||
@@ -224,6 +307,7 @@ core.start({
|
||||
},
|
||||
|
||||
update: function(dt) {
|
||||
highlight_selection()
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
function px(p) { return !is_null(p.x) ? p.x : p[0] }
|
||||
function py(p) { return !is_null(p.y) ? p.y : p[1] }
|
||||
|
||||
function grid(w, h) {
|
||||
var newgrid = meme(grid_prototype)
|
||||
newgrid.width = w;
|
||||
@@ -23,25 +26,28 @@ var grid_prototype = {
|
||||
|
||||
// alias for cell
|
||||
at(pos) {
|
||||
return this.cell(pos.x, pos.y);
|
||||
return this.cell(px(pos), py(pos));
|
||||
},
|
||||
|
||||
// add an entity into a cell
|
||||
add(entity, pos) {
|
||||
push(this.cell(pos.x, pos.y), entity);
|
||||
entity.coord = array(pos);
|
||||
var cx = px(pos), cy = py(pos);
|
||||
push(this.cell(cx, cy), entity);
|
||||
entity.coord = [cx, cy];
|
||||
},
|
||||
|
||||
// remove an entity from a cell
|
||||
remove(entity, pos) {
|
||||
this.cells[pos.y][pos.x] = filter(this.cells[pos.y][pos.x], x => x != entity)
|
||||
var cx = px(pos), cy = py(pos);
|
||||
this.cells[cy][cx] = filter(this.cells[cy][cx], x => x != entity)
|
||||
},
|
||||
|
||||
// bounds check
|
||||
inBounds(pos) {
|
||||
var cx = px(pos), cy = py(pos);
|
||||
return (
|
||||
pos.x >= 0 && pos.x < this.width &&
|
||||
pos.y >= 0 && pos.y < this.height
|
||||
cx >= 0 && cx < this.width &&
|
||||
cy >= 0 && cy < this.height
|
||||
);
|
||||
},
|
||||
|
||||
@@ -54,7 +60,7 @@ var grid_prototype = {
|
||||
for (x = 0; x < this.width; x++) {
|
||||
list = this.cells[y][x]
|
||||
arrfor(list, function(entity) {
|
||||
fn(entity, entity.coord);
|
||||
fn(entity);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -67,7 +73,7 @@ var grid_prototype = {
|
||||
var x = 0;
|
||||
for (y = 0; y < this.height; y++) {
|
||||
for (x = 0; x < this.width; x++) {
|
||||
out += length(this.cells[y][x]);
|
||||
out += text(length(this.cells[y][x]));
|
||||
}
|
||||
if (y != this.height - 1) out += "\n";
|
||||
}
|
||||
|
||||
@@ -19,15 +19,14 @@ var MovementSystem_prototype = {
|
||||
|
||||
var victims = this.grid.at(dest);
|
||||
if (length(victims) && victims[0].colour == piece.colour) return false;
|
||||
if (length(victims)) victims[0].captured = true;
|
||||
if (length(victims)) {
|
||||
victims[0].captured = true;
|
||||
this.grid.remove(victims[0], dest);
|
||||
}
|
||||
|
||||
this.grid.remove(piece, piece.coord);
|
||||
this.grid.add (piece, dest);
|
||||
|
||||
// grid.add() re-creates coord; re-add .x/.y fields:
|
||||
piece.coord.x = dest.x;
|
||||
piece.coord.y = dest.y;
|
||||
|
||||
this.turn = (this.turn == 'white') ? 'black' : 'white';
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ function startingPosition(grid) {
|
||||
|
||||
// pawns
|
||||
for (x = 0; x < 8; x++) {
|
||||
grid.add(Piece('pawn', W), [x, 6]);
|
||||
grid.add(Piece('pawn', B), [x, 1]);
|
||||
grid.add(Piece('pawn', W), [x, 1]);
|
||||
grid.add(Piece('pawn', B), [x, 6]);
|
||||
}
|
||||
// major pieces
|
||||
var back = ['rook','knight','bishop','queen','king','bishop','knight','rook'];
|
||||
for (x = 0; x < 8; x++) {
|
||||
grid.add(Piece(back[x], W), [x, 7]);
|
||||
grid.add(Piece(back[x], B), [x, 0]);
|
||||
grid.add(Piece(back[x], W), [x, 0]);
|
||||
grid.add(Piece(back[x], B), [x, 7]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ function cy(c) { return !is_null(c.y) ? c.y : c[1] }
|
||||
/* simple move-shape checks */
|
||||
var deltas = {
|
||||
pawn: function (pc, dx, dy, ctx) {
|
||||
var dir = (pc.colour == 'white') ? -1 : 1;
|
||||
var base = (pc.colour == 'white') ? 6 : 1;
|
||||
var dir = (pc.colour == 'white') ? 1 : -1;
|
||||
var base = (pc.colour == 'white') ? 1 : 6;
|
||||
var one = (dy == dir && dx == 0 && length(ctx.grid.at(ctx.to)) == 0);
|
||||
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
|
||||
length(ctx.grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir })) == 0 &&
|
||||
@@ -42,4 +42,4 @@ function canMove(piece, from, to, grid) {
|
||||
return clearLine(from, to, grid);
|
||||
}
|
||||
|
||||
return { canMove };
|
||||
return { canMove: canMove };
|
||||
|
||||
@@ -111,12 +111,12 @@ core.start({
|
||||
// Scoring
|
||||
if (bx < 0) {
|
||||
score2++
|
||||
score_label.text = score1 + " " + score2
|
||||
score_label.text = text(score1) + " " + text(score2)
|
||||
reset_ball()
|
||||
}
|
||||
if (bx > GW) {
|
||||
score1++
|
||||
score_label.text = score1 + " " + score2
|
||||
score_label.text = text(score1) + " " + text(score2)
|
||||
reset_ball()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -140,6 +140,7 @@ core.start({
|
||||
// New head position
|
||||
var hx = snake_pos[0].x + dir.x
|
||||
var hy = snake_pos[0].y + dir.y
|
||||
var tail = null
|
||||
|
||||
// Wrap
|
||||
if (hx < 0) hx = gridW - 1
|
||||
@@ -164,23 +165,27 @@ core.start({
|
||||
// Add head with tween from old head position
|
||||
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
|
||||
var new_wp = grid_to_world(hx, hy)
|
||||
snake_pos.unshift({x: hx, y: hy})
|
||||
var new_pos = [{x: hx, y: hy}]
|
||||
for (i = 0; i < length(snake_pos); i++) push(new_pos, snake_pos[i])
|
||||
snake_pos = new_pos
|
||||
var head = shape.rect({
|
||||
pos: {x: old_wp.x, y: old_wp.y}, width: cellSize - 2, height: cellSize - 2,
|
||||
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
|
||||
})
|
||||
// Smooth tween from old position to new position
|
||||
tw.tween(head.pos).to({x: new_wp.x, y: new_wp.y}, move_interval).ease(ease.linear)
|
||||
snake_shapes.unshift(head)
|
||||
var new_shapes = [head]
|
||||
for (i = 0; i < length(snake_shapes); i++) push(new_shapes, snake_shapes[i])
|
||||
snake_shapes = new_shapes
|
||||
|
||||
// Eat apple?
|
||||
if (hx == apple_gx && hy == apple_gy) {
|
||||
score++
|
||||
score_label.text = "Score: " + score
|
||||
score_label.text = "Score: " + text(score)
|
||||
spawn_apple()
|
||||
} else {
|
||||
// Remove tail
|
||||
var tail = pop(snake_shapes)
|
||||
tail = pop(snake_shapes)
|
||||
tail.destroy()
|
||||
pop(snake_pos)
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ var board_shapes = []
|
||||
function init_board() {
|
||||
board = []
|
||||
board_shapes = []
|
||||
var r = 0, c = 0
|
||||
var r = 0, c = 0, row = null, srow = null
|
||||
for (r = 0; r < ROWS; r++) {
|
||||
var row = []
|
||||
var srow = []
|
||||
row = []
|
||||
srow = []
|
||||
for (c = 0; c < COLS; c++) {
|
||||
push(row, null)
|
||||
push(srow, shape.rect({
|
||||
@@ -155,7 +155,7 @@ function rotate_blocks(blocks) {
|
||||
|
||||
function clear_lines() {
|
||||
var lines = 0
|
||||
var r = ROWS - 1, c = 0, full = false, newRow = null
|
||||
var r = ROWS - 1, c = 0, full = false, newRow = null, sr = 0
|
||||
while (r >= 0) {
|
||||
full = true
|
||||
for (c = 0; c < COLS; c++) {
|
||||
@@ -164,7 +164,7 @@ function clear_lines() {
|
||||
if (full) {
|
||||
lines++
|
||||
// Shift rows down
|
||||
var sr = r
|
||||
sr = r
|
||||
while (sr > 0) {
|
||||
for (c = 0; c < COLS; c++) {
|
||||
board[sr][c] = board[sr - 1][c]
|
||||
@@ -191,15 +191,15 @@ function clear_lines() {
|
||||
else if (lines == 4) score += 800
|
||||
linesCleared += lines
|
||||
level = floor(linesCleared / 10)
|
||||
score_label.text = "Score: " + score
|
||||
level_label.text = "Level: " + level
|
||||
score_label.text = "Score: " + text(score)
|
||||
level_label.text = "Level: " + text(level)
|
||||
}
|
||||
|
||||
function update_piece_shapes() {
|
||||
var i = 0
|
||||
var i = 0, bx = 0, by = 0
|
||||
for (i = 0; i < 4; i++) {
|
||||
var bx = pieceX + piece.blocks[i][0]
|
||||
var by = pieceY + piece.blocks[i][1]
|
||||
bx = pieceX + piece.blocks[i][0]
|
||||
by = pieceY + piece.blocks[i][1]
|
||||
piece_shapes[i].pos.x = bx * TILE + TILE / 2
|
||||
piece_shapes[i].pos.y = by * TILE + TILE / 2
|
||||
piece_shapes[i].fill = piece.color
|
||||
@@ -208,10 +208,10 @@ function update_piece_shapes() {
|
||||
}
|
||||
|
||||
function update_next_display() {
|
||||
var i = 0
|
||||
var i = 0, bx = 0, by = 0
|
||||
for (i = 0; i < 4; i++) {
|
||||
var bx = nextPiece.blocks[i][0]
|
||||
var by = nextPiece.blocks[i][1]
|
||||
bx = nextPiece.blocks[i][0]
|
||||
by = nextPiece.blocks[i][1]
|
||||
next_shapes[i].pos.x = (COLS + 1) * TILE + bx * TILE + TILE / 2
|
||||
next_shapes[i].pos.y = 20 + by * TILE + TILE / 2
|
||||
next_shapes[i].fill = nextPiece.color
|
||||
@@ -220,13 +220,14 @@ function update_next_display() {
|
||||
}
|
||||
|
||||
function spawn_piece() {
|
||||
var i = 0
|
||||
piece = nextPiece || random_shape()
|
||||
nextPiece = random_shape()
|
||||
pieceX = 3
|
||||
pieceY = 0
|
||||
if (collides(pieceX, pieceY, piece.blocks)) {
|
||||
gameOver = true
|
||||
var i = 0
|
||||
i = 0
|
||||
for (i = 0; i < 4; i++) piece_shapes[i].visible = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -880,7 +880,7 @@ const JSCFunctionListEntry js_imgui_funcs[] = {
|
||||
MIST_FUNC_DEF(imgui, menu, 2),
|
||||
MIST_FUNC_DEF(imgui, menubar, 1),
|
||||
MIST_FUNC_DEF(imgui, mainmenubar, 1),
|
||||
MIST_FUNC_DEF(imgui, menuitem, 3),
|
||||
MIST_FUNC_DEF(imgui, menuitem, 4),
|
||||
|
||||
// Input functions
|
||||
MIST_FUNC_DEF(imgui, textinput, 2),
|
||||
|
||||
2
input.cm
2
input.cm
@@ -83,7 +83,7 @@ function create_user(index, config) {
|
||||
|
||||
// Get action down state
|
||||
down() {
|
||||
return this.router ? this.router.down : {}
|
||||
return this.router ? this.router.down() : {}
|
||||
},
|
||||
|
||||
// Possess an entity (clears stack, sets as sole target)
|
||||
|
||||
@@ -1285,7 +1285,7 @@ function _execute_commands(commands, window_size) {
|
||||
current_pass = null
|
||||
}
|
||||
|
||||
_do_composite(cmd_buffer, cmd, window_size)
|
||||
_do_composite(cmd_buffer, cmd)
|
||||
|
||||
} else if (cmd.cmd == 'end_render') {
|
||||
// Flush pending draws
|
||||
@@ -1960,7 +1960,7 @@ function _get_font_cache(path, size, mode) {
|
||||
font =staef.sdf_font(data, size, 12.0, 14, 1.0)
|
||||
} else {
|
||||
// Bitmap
|
||||
font =staef.font(data, size, false)
|
||||
font =staef.font(data, size)
|
||||
}
|
||||
if (!font) {
|
||||
log.console(`sdl_gpu: Failed to load font ${path}:${size}:${local_mode}`)
|
||||
|
||||
64
shaders/msl/text_msdf.frag.msl
Normal file
64
shaders/msl/text_msdf.frag.msl
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct VertexOut {
|
||||
float4 position [[position]];
|
||||
float2 uv;
|
||||
float4 color;
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
float outline_width; // Outline width in normalized SDF units (0.0 = no outline, 0.1-0.3 typical)
|
||||
float sharpness; // Sharpness multiplier (1.0 = normal, higher = sharper)
|
||||
float2 _pad; // Padding for alignment
|
||||
float4 outline_color; // Outline color RGBA
|
||||
};
|
||||
|
||||
// Median of three values - used to combine MSDF channels
|
||||
float median(float r, float g, float b) {
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
fragment float4 fragment_main(VertexOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler smp [[sampler(0)]],
|
||||
constant Uniforms &u [[buffer(0)]]) {
|
||||
// Sample RGB channels from MSDF texture
|
||||
float3 msdf = tex.sample(smp, in.uv).rgb;
|
||||
|
||||
// Compute signed distance from the median of the three channels
|
||||
float dist = median(msdf.r, msdf.g, msdf.b);
|
||||
|
||||
// Edge is at 0.5 (where distance = 0 in the original SDF)
|
||||
float edge = 0.5;
|
||||
|
||||
// Calculate anti-aliasing width based on screen-space derivatives
|
||||
// MSDF typically needs tighter AA than single-channel SDF
|
||||
float aa = fwidth(dist);
|
||||
float sharpness = max(u.sharpness, 0.1);
|
||||
aa = aa / sharpness;
|
||||
|
||||
// Fill coverage: inside the glyph
|
||||
float fill = smoothstep(edge - aa, edge + aa, dist);
|
||||
|
||||
// Outline coverage: extends outward from the edge
|
||||
float outline_coverage = 0.0;
|
||||
if (u.outline_width > 0.0) {
|
||||
float outline_edge = edge - u.outline_width;
|
||||
outline_coverage = smoothstep(outline_edge - aa, outline_edge + aa, dist);
|
||||
}
|
||||
|
||||
// Total coverage is the union of fill and outline
|
||||
float total_coverage = max(fill, outline_coverage);
|
||||
|
||||
// Blend colors: outline where not filled, fill color where filled
|
||||
float3 rgb = in.color.rgb;
|
||||
if (u.outline_width > 0.0) {
|
||||
rgb = mix(u.outline_color.rgb, in.color.rgb, fill);
|
||||
}
|
||||
|
||||
// Final alpha combines coverage with vertex alpha
|
||||
float a = total_coverage * in.color.a;
|
||||
|
||||
return float4(rgb, a);
|
||||
}
|
||||
59
shaders/msl/text_sdf.frag.msl
Normal file
59
shaders/msl/text_sdf.frag.msl
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct VertexOut {
|
||||
float4 position [[position]];
|
||||
float2 uv;
|
||||
float4 color;
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
float outline_width; // Outline width in normalized SDF units (0.0 = no outline, 0.1-0.3 typical)
|
||||
float sharpness; // Sharpness multiplier (1.0 = normal, higher = sharper)
|
||||
float2 _pad; // Padding for alignment
|
||||
float4 outline_color; // Outline color RGBA
|
||||
};
|
||||
|
||||
fragment float4 fragment_main(VertexOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler smp [[sampler(0)]],
|
||||
constant Uniforms &u [[buffer(0)]]) {
|
||||
// Sample distance from alpha channel (SDF fonts store distance in alpha)
|
||||
float dist = tex.sample(smp, in.uv).a;
|
||||
|
||||
// Edge is at 0.5 (where distance = 0 in the original SDF)
|
||||
float edge = 0.5;
|
||||
|
||||
// Calculate anti-aliasing width based on screen-space derivatives
|
||||
// Sharpness multiplier controls how tight the AA band is
|
||||
float aa = fwidth(dist);
|
||||
float sharpness = max(u.sharpness, 0.1); // Prevent division issues
|
||||
aa = aa / sharpness;
|
||||
|
||||
// Fill coverage: inside the glyph
|
||||
float fill = smoothstep(edge - aa, edge + aa, dist);
|
||||
|
||||
// Outline coverage: extends outward from the edge
|
||||
float outline_coverage = 0.0;
|
||||
if (u.outline_width > 0.0) {
|
||||
// Outline edge is further out (lower distance value = further from glyph center)
|
||||
float outline_edge = edge - u.outline_width;
|
||||
outline_coverage = smoothstep(outline_edge - aa, outline_edge + aa, dist);
|
||||
}
|
||||
|
||||
// Total coverage is the union of fill and outline
|
||||
float total_coverage = max(fill, outline_coverage);
|
||||
|
||||
// Blend colors: outline where not filled, fill color where filled
|
||||
// fill goes from 0 (outside glyph) to 1 (inside glyph)
|
||||
// outline_coverage goes from 0 (outside outline) to 1 (inside outline+glyph)
|
||||
float3 rgb = in.color.rgb;
|
||||
if (u.outline_width > 0.0) {
|
||||
rgb = mix(u.outline_color.rgb, in.color.rgb, fill);
|
||||
}
|
||||
|
||||
// Final alpha combines coverage with vertex alpha
|
||||
float a = total_coverage * in.color.a;
|
||||
|
||||
return float4(rgb, a);
|
||||
}
|
||||
Reference in New Issue
Block a user