probe
This commit is contained in:
@@ -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))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user