317 lines
8.4 KiB
Plaintext
317 lines
8.4 KiB
Plaintext
var core = use('core')
|
|
var camera = use('camera')
|
|
var compositor = use('compositor')
|
|
var input = use('input')
|
|
var shape = use('shape2d')
|
|
var text2d = use('text2d')
|
|
|
|
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}})
|
|
|
|
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, row = null, col = null
|
|
for (by = 0; by < 8; by++) {
|
|
row = []
|
|
for (bx = 0; bx < 8; bx++) {
|
|
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 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_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},
|
|
size: 36,
|
|
color: (p.colour == 'white') ? white_color : black_color,
|
|
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, col = null
|
|
for (y = 0; y < 8; y++) {
|
|
for (x = 0; x < 8; x++) {
|
|
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
|
|
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, m = null
|
|
for (i = 0; i < length(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
|
|
}
|
|
}
|
|
}
|
|
|
|
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() {
|
|
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 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') {
|
|
selectPos = null
|
|
validMoves = []
|
|
highlight_selection()
|
|
return
|
|
}
|
|
|
|
if (action == 'select' && hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
|
|
clicked = [hover_gx, hover_gy]
|
|
cell = grid.at(clicked)
|
|
|
|
if (selectPos) {
|
|
// Try to move
|
|
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
|
|
break
|
|
}
|
|
}
|
|
|
|
if (is_valid) {
|
|
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()
|
|
}
|
|
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)
|
|
|
|
// --- 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: [
|
|
{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", probe: true,
|
|
|
|
input: function(ev) {
|
|
var wp = null
|
|
if (ev.type == 'mouse_motion') {
|
|
// Convert pixel coords to grid coords via camera
|
|
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)
|
|
}
|
|
}
|
|
},
|
|
|
|
update: function(dt) {
|
|
highlight_selection()
|
|
},
|
|
|
|
render: function() {
|
|
return compositor.execute(compositor.compile(comp_config))
|
|
}
|
|
})
|