This commit is contained in:
2026-02-24 21:08:46 -06:00
parent f87854fca1
commit 3d87fdeb5f
23 changed files with 1551 additions and 1086 deletions

View File

@@ -1,403 +1,232 @@
/* main.js runs the demo with your prototype-based grid */
var core = use('core')
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 json = use('json')
var draw2d = use('prosperon/draw2d')
var Grid = use('grid')
var MovementSystem = use('movement').MovementSystem
var startingPos = use('pieces').startingPosition
var rules = use('rules')
var blob = use('blob')
var S = 60
var GW = S * 8, GH = S * 8
/*──── import our pieces + systems ───────────────────────────────────*/
var Grid = use('grid'); // your new ctor
var MovementSystem = use('movement').MovementSystem;
var startingPos = use('pieces').startingPosition;
var rules = use('rules');
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}})
/*──── build board ───────────────────────────────────────────────────*/
var grid = Grid(8, 8);
grid.width = 8; // (the ctor didn't store them)
grid.height = 8;
var mover = MovementSystem(grid, rules);
startingPos(grid);
/*──── networking and game state ─────────────────────────────────────*/
var gameState = 'waiting'; // 'waiting', 'searching', 'server_waiting', 'connected'
var isServer = false;
var opponent = null;
var myColor = null; // 'white' or 'black'
var isMyTurn = false;
function updateTitle() {
var title = "Misty Chess - ";
if (gameState == 'waiting') {
title += "Press S to start server or J to join";
} else if (gameState == 'searching') {
title += "Searching for server...";
} else if (gameState == 'server_waiting') {
title += "Waiting for player to join...";
} else if (gameState == 'connected') {
if (myColor) {
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
} else {
title += mover.turn + " turn";
}
}
log.console(title)
}
// Initialize title
updateTitle();
/*──── mouse → click-to-move ─────────────────────────────────────────*/
var selectPos = null;
var hoverPos = null;
var holdingPiece = false;
var opponentMousePos = null;
var opponentHoldingPiece = false;
var opponentSelectPos = null;
function handleMouseButtonDown(e) {
if (e.which != 0) return;
// Don't allow piece selection unless we have an opponent
if (gameState != 'connected' || !opponent) return;
var mx = e.mouse.x;
var my = e.mouse.y;
var c = [floor(mx / 60), floor(my / 60)];
if (!grid.inBounds(c)) return;
var cell = grid.at(c);
if (length(cell) && cell[0].colour == mover.turn) {
selectPos = c;
holdingPiece = true;
// Send pickup notification to opponent
if (opponent) {
send(opponent, {
type: 'piece_pickup',
pos: c
});
}
} else {
selectPos = null;
}
}
function handleMouseButtonUp(e) {
if (e.which != 0 || !holdingPiece || !selectPos) return;
// Don't allow moves unless we have an opponent and it's our turn
if (gameState != 'connected' || !opponent || !isMyTurn) {
holdingPiece = false;
return;
}
var mx = e.mouse.x;
var my = e.mouse.y;
var c = [floor(mx / 60), floor(my / 60)];
if (!grid.inBounds(c)) {
holdingPiece = false;
return;
}
if (mover.tryMove(grid.at(selectPos)[0], c)) {
log.console("Made move from", selectPos, "to", c);
// Send move to opponent
log.console("Sending move to opponent:", opponent);
send(opponent, {
type: 'move',
from: selectPos,
to: c
});
isMyTurn = false; // It's now opponent's turn
log.console("Move sent, now opponent's turn");
selectPos = null;
updateTitle();
}
holdingPiece = false;
// Send piece drop notification to opponent
if (opponent) {
send(opponent, {
type: 'piece_drop'
});
}
}
function handleMouseMotion(e) {
var mx = e.pos.x;
var my = e.pos.y;
var c = [floor(mx / 60), floor(my / 60)];
if (!grid.inBounds(c)) {
hoverPos = null;
return;
}
hoverPos = c;
// Send mouse position to opponent in real-time
if (opponent && gameState == 'connected') {
send(opponent, {
type: 'mouse_move',
pos: c,
holding: holdingPiece,
selectPos: selectPos
});
}
}
function handleKeyDown(e) {
// S key - start server
if (e.scancode == 22 && gameState == 'waiting') { // S key
startServer();
}
// J key - join server
else if (e.scancode == 13 && gameState == 'waiting') { // J key
joinServer();
}
}
/*──── drawing helpers ───────────────────────────────────────────────*/
/* ── constants ─────────────────────────────────────────────────── */
var S = 60; // square size in px
var light = [0.93,0.93,0.93,1];
var dark = [0.25,0.25,0.25,1];
var allowedColor = [1.0, 0.84, 0.0, 1.0]; // Gold for allowed moves
var myMouseColor = [0.0, 1.0, 0.0, 1.0]; // Green for my mouse
var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
/* ── draw one 8×8 chess board ──────────────────────────────────── */
function drawBoard() {
var y = 0;
var x = 0;
var isMyHover = null;
var isOpponentHover = null;
var isValidMove = null;
var color = null;
for (y = 0; y < 8; ++y)
for (x = 0; x < 8; ++x) {
isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
color = ((x+y)&1) ? dark : light;
if (isValidMove) {
color = allowedColor; // Gold for allowed moves
} else if (isMyHover && !isOpponentHover) {
color = myMouseColor; // Green for my mouse
} else if (isOpponentHover) {
color = opponentMouseColor; // Red for opponent mouse
}
draw2d.rectangle(
{ x: x*S, y: y*S, width: S, height: S },
{ thickness: 0 },
{ color: color }
);
}
}
function isValidMoveForTurn(from, to) {
if (!grid.inBounds(to)) return false;
var piece = grid.at(from)[0];
if (!piece) return false;
// Check if the destination has a piece of the same color
var destCell = grid.at(to);
if (length(destCell) && destCell[0].colour == piece.colour) {
return false;
}
return rules.canMove(piece, from, to, grid);
}
/* ── draw every live piece ─────────────────────────────────────── */
function drawPieces() {
var piece = null;
var r = null;
var opponentPiece = null;
grid.each(function (p) {
if (p.captured) return;
// Skip drawing the piece being held (by me or opponent)
if (holdingPiece && selectPos &&
p.coord[0] == selectPos[0] &&
p.coord[1] == selectPos[1]) {
return;
}
// Skip drawing the piece being held by opponent
if (opponentHoldingPiece && opponentSelectPos &&
p.coord[0] == opponentSelectPos[0] &&
p.coord[1] == opponentSelectPos[1]) {
return;
}
var pr = { x: p.coord[0]*S, y: p.coord[1]*S,
width:S, height:S };
draw2d.image(p.sprite, pr);
});
// Draw the held piece at the mouse position if we're holding one
if (holdingPiece && selectPos && hoverPos) {
piece = grid.at(selectPos)[0];
if (piece) {
r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
width:S, height:S };
draw2d.image(piece.sprite, r);
}
}
// Draw opponent's held piece if they're dragging one
if (opponentHoldingPiece && opponentSelectPos && opponentMousePos) {
opponentPiece = grid.at(opponentSelectPos)[0];
if (opponentPiece) {
r = { x: opponentMousePos[0]*S, y: opponentMousePos[1]*S,
width:S, height:S };
// Draw with slight transparency to show it's the opponent's piece
draw2d.image(opponentPiece.sprite, r);
}
}
}
function update(dt)
{
return {}
}
function draw()
{
draw2d.clear()
drawBoard()
drawPieces()
return draw2d.get_commands()
}
function startServer() {
gameState = 'server_waiting';
isServer = true;
myColor = 'white';
isMyTurn = true;
updateTitle();
$portal(e => {
log.console("Portal received contact message");
// Reply with this actor to establish connection
log.console ($self)
send(e, $self);
log.console("Portal replied with server actor");
}, 5678);
}
function joinServer() {
gameState = 'searching';
updateTitle();
function contact_fn(actor, reason) {
log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
if (actor) {
opponent = actor;
log.console("Connection established with server, sending join request");
// Send a greet message with our actor object
send(opponent, {
type: 'greet',
client_actor: $self
});
} else {
log.console(`Failed to connect: ${reason}`);
gameState = 'waiting';
updateTitle();
}
}
$contact(contact_fn, {
address: "192.168.0.149",
port: 5678
});
}
$receiver(e => {
var fromCell = null;
var piece = null;
if (e.kind == 'update')
send(e, update(e.dt))
else if (e.kind == 'draw')
send(e, draw())
else if (e.type == 'game_start' || e.type == 'move' || e.type == 'greet')
log.console("Receiver got message:", e.type, e);
if (e.type == 'greet') {
log.console("Server received greet from client");
// Store the client's actor object for ongoing communication
opponent = e.client_actor;
log.console("Stored client actor:", opponent);
gameState = 'connected';
updateTitle();
// Send game_start to the client
log.console("Sending game_start to client");
send(opponent, {
type: 'game_start',
your_color: 'black'
});
log.console("game_start message sent to client");
}
else if (e.type == 'game_start') {
log.console("Game starting, I am:", e.your_color);
myColor = e.your_color;
isMyTurn = (myColor == 'white');
gameState = 'connected';
updateTitle();
} else if (e.type == 'move') {
log.console("Received move from opponent:", e.from, "to", e.to);
// Apply opponent's move
fromCell = grid.at(e.from);
if (length(fromCell)) {
piece = fromCell[0];
if (mover.tryMove(piece, e.to)) {
isMyTurn = true; // It's now our turn
updateTitle();
log.console("Applied opponent move, now my turn");
} else {
log.console("Failed to apply opponent move");
}
} else {
log.console("No piece found at from position");
}
} else if (e.type == 'mouse_move') {
// Update opponent's mouse position
opponentMousePos = e.pos;
opponentHoldingPiece = e.holding;
opponentSelectPos = e.selectPos;
} else if (e.type == 'piece_pickup') {
// Opponent picked up a piece
opponentSelectPos = e.pos;
opponentHoldingPiece = true;
} else if (e.type == 'piece_drop') {
// Opponent dropped their piece
opponentHoldingPiece = false;
opponentSelectPos = null;
} else if (e.type == 'mouse_button_down') {
handleMouseButtonDown(e)
} else if (e.type == 'mouse_button_up') {
handleMouseButtonUp(e)
} else if (e.type == 'mouse_motion') {
handleMouseMotion(e)
} else if (e.type == 'key_down') {
handleKeyDown(e)
input.configure({
action_map: {
select: ['mouse_button_left'],
cancel: ['escape', 'mouse_button_right']
}
})
// Build board
var grid = Grid(8, 8)
grid.width = 8
grid.height = 8
var mover = MovementSystem(grid, rules)
startingPos(grid)
// Board squares (shape2d)
var light_color = {r: 0.93, g: 0.93, b: 0.85, a: 1}
var dark_color = {r: 0.45, g: 0.55, b: 0.35, a: 1}
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
for (by = 0; by < 8; by++) {
var row = []
for (bx = 0; bx < 8; bx++) {
var 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,
fill: {r: col.r, g: col.g, b: col.b, a: col.a},
plane: 'game', layer: 0
}))
}
push(board_shapes, row)
}
// Piece sprites — one per piece, keyed by piece object
var piece_sprites = {}
var piece_id = 0
grid.each(function(p) {
piece_id++
p._id = piece_id
piece_sprites[piece_id] = sprite_factory({
image: p.sprite,
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,
plane: 'game', layer: 1
})
})
// Selection state
var selectPos = null
var validMoves = []
// Mouse position in grid coords
var hover_gx = -1, hover_gy = -1
// Status text
var status_label = text2d({
text: "White's turn", pos: {x: 10, y: GH - 20},
plane: 'hud', size: 14, color: {r: 1, g: 1, b: 1, a: 1}
})
function update_status() {
status_label.text = mover.turn + "'s turn"
}
function reset_board_colors() {
var x = 0, y = 0
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
var 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}
}
}
}
function highlight_selection() {
reset_board_colors()
if (!selectPos) return
// Highlight selected square
board_shapes[selectPos[1]][selectPos[0]].fill = {
r: select_color.r, g: select_color.g, b: select_color.b, a: select_color.a
}
// Highlight valid moves
var i = 0
for (i = 0; i < length(validMoves); i++) {
var 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
}
}
}
function compute_valid_moves(from) {
validMoves = []
var piece = grid.at(from)[0]
if (!piece) return
var x = 0, y = 0, to = null, dest = null
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
to = [x, y]
dest = grid.at(to)
if (length(dest) && dest[0].colour == piece.colour) continue
if (rules.canMove(piece, from, to, grid))
push(validMoves, to)
}
}
}
function sync_piece_sprites() {
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
}
})
}
// Input handler
var game_input = {
on_input: function(action, data) {
if (!data.pressed) return
if (action == 'cancel') {
selectPos = null
validMoves = []
highlight_selection()
return
}
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)
if (selectPos) {
// Try to move
var is_valid = false
var i = 0
for (i = 0; i < length(validMoves); i++) {
if (validMoves[i][0] == clicked[0] && validMoves[i][1] == clicked[1]) {
is_valid = true
break
}
}
if (is_valid) {
var src_piece = grid.at(selectPos)[0]
if (src_piece && mover.tryMove(src_piece, clicked)) {
sync_piece_sprites()
update_status()
}
selectPos = null
validMoves = []
} else if (length(cell) && cell[0].colour == mover.turn) {
// Select different piece
selectPos = clicked
compute_valid_moves(selectPos)
} else {
selectPos = null
validMoves = []
}
} else {
// Select piece
if (length(cell) && cell[0].colour == mover.turn) {
selectPos = clicked
compute_valid_moves(selectPos)
}
}
highlight_selection()
}
}
}
input.player1().possess(game_input)
var comp_config = {
clear: {r: 0.15, g: 0.15, b: 0.2, a: 1},
planes: [
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
{name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
]
}
core.start({
width: 640, height: 640, title: "Chess",
input: function(ev) {
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)
if (wp) {
hover_gx = floor(wp.x / S)
hover_gy = floor(wp.y / S)
}
}
},
update: function(dt) {
},
render: function() {
return compositor.execute(compositor.compile(comp_config))
}
})

View File

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