Created
October 4, 2018 20:31
-
-
Save edg-l/d1f52f7883792e642442fa8f5d439847 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function(c, a) | |
{ | |
a = a || {} | |
var r = #fs.multivac.renderer() | |
var clone = #fs.scripts.lib().clone | |
var log = [] | |
var squareWidth = 9 | |
var squareHeight = 7 | |
var boardRect = r.Rect(2, 2, 8 * squareWidth, 8 * squareHeight) | |
const words = "abcdefgh" | |
const numbers = "87654321" | |
const EMPTY = -1 | |
const WHITE_PAWN = 0 | |
const WHITE_KNIGHT = 1 | |
const WHITE_BISHOP = 2 | |
const WHITE_ROOK = 3 | |
const WHITE_KING = 4 | |
const WHITE_QUEEN = 5 | |
const BLACK_PAWN = 6 | |
const BLACK_KNIGHT = 7 | |
const BLACK_BISHOP = 8 | |
const BLACK_ROOK = 9 | |
const BLACK_KING = 10 | |
const BLACK_QUEEN = 11 | |
function onlyUnique(value, index, self) { | |
return self.indexOf(value) === index; | |
} | |
var START_BOARD = { | |
a8: BLACK_ROOK, b8: BLACK_KNIGHT, c8: BLACK_BISHOP, d8: BLACK_QUEEN, e8: BLACK_KING, f8: BLACK_BISHOP, g8: BLACK_KNIGHT, h8: BLACK_ROOK, | |
a7: BLACK_PAWN, b7: BLACK_PAWN, c7: BLACK_PAWN, d7: BLACK_PAWN, e7: BLACK_PAWN, f7: BLACK_PAWN, g7: BLACK_PAWN, h7: BLACK_PAWN, | |
a6: EMPTY, b6: EMPTY, c6: EMPTY, d6: EMPTY, e6: EMPTY, f6: EMPTY, g6: EMPTY, h6: EMPTY, | |
a5: EMPTY, b5: EMPTY, c5: EMPTY, d5: EMPTY, e5: EMPTY, f5: EMPTY, g5: EMPTY, h5: EMPTY, | |
a4: EMPTY, b4: EMPTY, c4: EMPTY, d4: EMPTY, e4: EMPTY, f4: EMPTY, g4: EMPTY, h4: EMPTY, | |
a3: EMPTY, b3: EMPTY, c3: EMPTY, d3: EMPTY, e3: EMPTY, f3: EMPTY, g3: EMPTY, h3: EMPTY, | |
a2: WHITE_PAWN, b2: WHITE_PAWN, c2: WHITE_PAWN, d2: WHITE_PAWN, e2: WHITE_PAWN, f2: WHITE_PAWN, g2: WHITE_PAWN, h2: WHITE_PAWN, | |
a1: WHITE_ROOK, b1: WHITE_KNIGHT, c1: WHITE_BISHOP, d1: WHITE_QUEEN, e1: WHITE_KING, f1: WHITE_BISHOP, g1: WHITE_KNIGHT, h1: WHITE_ROOK, | |
} | |
r.initialize(4 + boardRect.w + 35, 4 + boardRect.h + 4, " ") | |
var drawBoard = () => { | |
var index = 1 | |
for(var i = 0; i < boardRect.h; i+=squareHeight) { | |
for(var j = 0; j < boardRect.w; j+=squareWidth) { | |
if(index % 2 == 0) | |
r.draw(r.FilledRect(j + boardRect.x, i + boardRect.y, squareWidth, squareHeight, "█", "F")) | |
else | |
r.draw(r.FilledRect(j + boardRect.x, i + boardRect.y, squareWidth, squareHeight, "█", "G")) | |
index++ | |
} | |
index++ | |
} | |
// Draw numbers | |
var numbersIndex = 0 | |
for(var i = parseInt(squareHeight / 2); i < boardRect.h; i+=squareHeight) { | |
r.draw(r.String(boardRect.x - 2, i + boardRect.y, numbers[numbersIndex], "A")) | |
numbersIndex++ | |
} | |
// Draw words | |
var wordIndex = 0 | |
for(var i = parseInt(squareWidth / 2); i < boardRect.w; i+=squareWidth) { | |
r.draw(r.String(i + boardRect.x, boardRect.y + boardRect.h + 1, words[wordIndex], "A")) | |
wordIndex++ | |
} | |
} | |
var drawPiece = (word, num, piece, color = "white") => { | |
var colorChar | |
if(color == "white") { | |
colorChar = "A" | |
} else { | |
colorChar = "X" | |
} | |
if(words.indexOf(word) === -1 || numbers.indexOf(num) === -1 || !pieces[piece]) | |
throw new Error(`Invalid movement ${word}${num} or piece ${piece}`) // TODO: Change this | |
r.draw(r.Pattern(1 + boardRect.x + squareWidth * words.indexOf(word), 1 + boardRect.y | |
+ squareHeight * numbers.indexOf(num), pieces[piece], ["|", "/", "\\", "T", "U", "-", "+"], colorChar)) | |
} | |
var numberToPieceInfo = (num) => { | |
var color | |
if(num >= 0 && num < 6) | |
color = "white" | |
else | |
color = "black" | |
var piece | |
switch(num) { | |
case -1: | |
piece = "" | |
color = false | |
break | |
case 0: | |
case 6: | |
piece = "pawn" | |
break | |
case 1: | |
case 7: | |
piece = "knight" | |
break | |
case 2: | |
case 8: | |
piece = "bishop" | |
break | |
case 3: | |
case 9: | |
piece = "rook" | |
break | |
case 4: | |
case 10: | |
piece = "king" | |
break | |
case 5: | |
case 11: | |
piece = "queen" | |
break | |
} | |
return { | |
piece: piece, | |
color: color | |
} | |
} | |
var drawPiecesFromBoardInfo = (boardInfo) => { | |
for(var i in boardInfo) { | |
if(boardInfo[i] !== -1) { | |
var word = i[0] | |
var num = i[1] | |
var info = numberToPieceInfo(boardInfo[i]) | |
drawPiece(word, num, info.piece, info.color) | |
} | |
} | |
} | |
// https://en.wikipedia.org/wiki/Portable_Game_Notation | |
// b5 c6, if promotion a7 a8=Q | |
// The letter abbreviations are K (king), Q (queen), R (rook), B (bishop), and N (knight). | |
// User doesnt have to add abbr only from to | |
var interpretOperation = (op) => { | |
const promotionRegex = /^([a-h])([1-8]) ([a-h])([1-8])=([KQRBN])$/ | |
const moveRegex = /^([a-h])([1-8]) ([a-h])([1-8])$/ | |
op = op.trim() | |
if(promotionRegex.test(op)) { | |
// Promotion move | |
var m = promotionRegex.exec(op) | |
return { | |
from: { | |
word: m[1], | |
num: m[2] | |
}, | |
to: { | |
word: m[3], | |
num: m[4], | |
promotion: m[5] | |
}, | |
} | |
} | |
if(moveRegex.test(op)) { | |
// Promotion move | |
var m = moveRegex.exec(op) | |
return { | |
from: { | |
word: m[1], | |
num: m[2] | |
}, | |
to: { | |
word: m[3], | |
num: m[4], | |
}, | |
} | |
} | |
} | |
var positionExists = (word, num) => { | |
var index = words.indexOf(word) | |
return index >= 0 && index < words.length && num >= 1 && num <= 8 | |
} | |
var wordMoveExists = (word, indexMove) => { | |
var index = words.indexOf(word) + indexMove | |
return index >= 0 && index < words.length | |
} | |
var getWord = (word, indexMove) => { | |
return words[words.indexOf(word) + indexMove] | |
} | |
// Color == white indicates if white is checking black king | |
var isPositionCheck = (board, color = "white") => { | |
var whiteKingPos, blackKingPos | |
for(var x in board) { | |
if(board[x] == WHITE_KING) | |
whiteKingPos = x | |
if(board[x] == BLACK_KING) | |
blackKingPos = x | |
} | |
for(var x in board) { | |
if(board[x] == EMPTY) | |
continue | |
var moves = getValidMoves(board, {word: x[0], num: x[1]}, color, true, true, false) | |
if(moves) { | |
for(var m in moves) { | |
if(moves[m] == (color == "white" ? blackKingPos : whiteKingPos)) { | |
return true | |
} | |
} | |
} | |
} | |
return false | |
} | |
var moveFixesCheck = (board, move, playerColor = "white") => { | |
let checkBoard = clone(board) | |
var piece = getBoardPiece(board, move.from) | |
setBoardPiece(checkBoard, move.from, EMPTY) | |
setBoardPiece(checkBoard, move.to, piece) | |
return !isPositionCheck(checkBoard, playerColor == "white" ? "black" : "white") | |
} | |
var isCheckMate = (board, playerColor = "white") => { | |
var checkmate = true | |
for(var x in board) { | |
if(board[x] == EMPTY) | |
continue | |
var moves = getValidMoves(board, {word: x[0], num: x[1]}, playerColor) | |
if(moves) { | |
for(var m in moves) { | |
if(moveFixesCheck(board, {from:{word:x[0], num:x[1]}, to:{word:moves[m][0], num:moves[m][1]}}, playerColor)) { | |
checkmate = false | |
} | |
} | |
} | |
} | |
return checkmate | |
} | |
var getValidMoves = (board, piecePos, playerColor = "white", includePawnCapture = false, excludePawnForward = false, checkKingCheck = true) => { | |
var info = numberToPieceInfo(board[piecePos.word + piecePos.num]) | |
if(info.color !== playerColor) { | |
return false | |
} | |
var needCheckFix = false | |
// Check if player is being checked | |
if(checkKingCheck) { | |
needCheckFix = isPositionCheck(board, playerColor == "white" ? "black" : "white") | |
checkKingCheck = false | |
} | |
// Check for pinned pieces | |
let checkBoard = clone(board) | |
setBoardPiece(checkBoard, piecePos, EMPTY) | |
if(checkKingCheck && isPositionCheck(checkBoard, playerColor == "white" ? "black" : "white")) { | |
needCheckFix = true | |
} | |
var validMoves = [] | |
switch(info.piece) { | |
case "pawn": { | |
var checkValue = playerColor === "white" ? 1 : -1 | |
var pawnInitial = playerColor === "white" ? "2" : "7" | |
// Check if pawn can go forward | |
if(piecePos.num < 8 && piecePos.num > 1 && board[piecePos.word + (parseInt(piecePos.num) + checkValue)] === EMPTY && !excludePawnForward) { | |
validMoves.push(piecePos.word + (parseInt(piecePos.num) + checkValue)) | |
} | |
// Check if pawn is at initial pos and can go forward 2 times | |
if(piecePos.num == pawnInitial && board[piecePos.word + (parseInt(piecePos.num) + checkValue * 2)] === EMPTY && !excludePawnForward) { | |
validMoves.push(piecePos.word + (parseInt(piecePos.num) + checkValue * 2)) | |
} | |
// Check if pawn capture piece | |
// Check if its not on left wall | |
if(wordMoveExists(piecePos.word, -1)) { | |
var posInfo = numberToPieceInfo(board[getWord(piecePos.word, -1) + (parseInt(piecePos.num) + checkValue)]) | |
if(includePawnCapture || (posInfo.piece !== "" && posInfo.piece.color !== playerColor)) | |
validMoves.push(getWord(piecePos.word, -1) + (parseInt(piecePos.num) + checkValue)) | |
} | |
// Check if its not on right wall | |
if(wordMoveExists(piecePos.word, 1)) { | |
var posInfo = numberToPieceInfo(board[getWord(piecePos.word, 1) + (parseInt(piecePos.num) + checkValue)]) | |
if(includePawnCapture || (posInfo.piece !== "" && posInfo.piece.color !== playerColor)) | |
validMoves.push(getWord(piecePos.word, 1) + (parseInt(piecePos.num) + checkValue)) | |
} | |
// TODO: Add pawn promotion, i think pawn promotion is handled outside this | |
// TODO: Add En passant, for this i need to know previus move of opposite color | |
// which can be done when i have the match log? | |
break | |
} | |
case "knight": { | |
// --^ | |
var checkKnight = (x, y) => { | |
if(wordMoveExists(piecePos.word, x) && positionExists(getWord(piecePos.word, x), parseInt(piecePos.num) + y)) { | |
var pos = getWord(piecePos.word, x) + (parseInt(piecePos.num) + y) | |
var posInfo = numberToPieceInfo(board[pos]) | |
if(posInfo.piece == "" || posInfo.color !== playerColor) | |
validMoves.push(pos) | |
} | |
} | |
checkKnight(2, 1) | |
checkKnight(2, -1) | |
checkKnight(-2, 1) | |
checkKnight(-2, -1) | |
checkKnight(1, 2) | |
checkKnight(-1, 2) | |
checkKnight(1, -2) | |
checkKnight(-1, -2) | |
break | |
} | |
case "bishop": { | |
var checkBishop = (xsign = 1, ysign = 1) => { | |
var i = 1 | |
while(true) { | |
if(wordMoveExists(piecePos.word, i * xsign) && positionExists(getWord(piecePos.word, i * xsign), parseInt(piecePos.num) + i * ysign)) { | |
var pos = getWord(piecePos.word, i * xsign) + (parseInt(piecePos.num) + i * ysign) | |
var posInfo = numberToPieceInfo(board[pos]) | |
i++ | |
if(posInfo.piece == "") { | |
validMoves.push(pos) | |
continue | |
} | |
if(posInfo.color !== playerColor) { | |
validMoves.push(pos) | |
break | |
} else { | |
break | |
} | |
} else { | |
break | |
} | |
} | |
} | |
checkBishop(1, 1) | |
checkBishop(1, -1) | |
checkBishop(-1, -1) | |
checkBishop(-1, 1) | |
break | |
} | |
case "king": { | |
var checkKing = (x, y) => { | |
if(wordMoveExists(piecePos.word, x) && positionExists(getWord(piecePos.word, x), parseInt(piecePos.num) + y)) { | |
var pos = getWord(piecePos.word, x) + (parseInt(piecePos.num) + y) | |
var posInfo = numberToPieceInfo(board[pos]) | |
if(posInfo.piece == "" || posInfo.color !== playerColor) | |
validMoves.push(pos) | |
} | |
} | |
checkKing(1, 1) | |
checkKing(1, -1) | |
checkKing(-1, -1) | |
checkKing(-1, 1) | |
checkKing(0, 1) | |
checkKing(0, -1) | |
checkKing(1, 0) | |
checkKing(-1, 0) | |
break | |
} | |
case "rook": { | |
var checkRook = (xsign = 1, ysign = 1) => { | |
var i = 1 | |
while(true) { | |
if(wordMoveExists(piecePos.word, i * xsign) && positionExists(getWord(piecePos.word, i * xsign), parseInt(piecePos.num) + i * ysign)) { | |
var pos = getWord(piecePos.word, i * xsign) + (parseInt(piecePos.num) + i * ysign) | |
var posInfo = numberToPieceInfo(board[pos]) | |
i++ | |
if(posInfo.piece == "") { | |
validMoves.push(pos) | |
continue | |
} | |
if(posInfo.color !== playerColor) { | |
validMoves.push(pos) | |
break | |
} else { | |
break | |
} | |
} else { | |
break | |
} | |
} | |
} | |
checkRook(0, 1) | |
checkRook(0, -1) | |
checkRook(1, 0) | |
checkRook(-1, 0) | |
break | |
} | |
case "queen": { | |
var checkQueen = (x, y) => { | |
if(wordMoveExists(piecePos.word, x) && positionExists(getWord(piecePos.word, x), parseInt(piecePos.num) + y)) { | |
var pos = getWord(piecePos.word, x) + (parseInt(piecePos.num) + y) | |
var posInfo = numberToPieceInfo(board[pos]) | |
if(posInfo.piece == "" || posInfo.color !== playerColor) | |
validMoves.push(pos) | |
} | |
} | |
var checkQueenDiag = (xsign = 1, ysign = 1) => { | |
var i = 1 | |
while(true) { | |
if(wordMoveExists(piecePos.word, i * xsign) && positionExists(getWord(piecePos.word, i * xsign), parseInt(piecePos.num) + i * ysign)) { | |
var pos = getWord(piecePos.word, i * xsign) + (parseInt(piecePos.num) + i * ysign) | |
var posInfo = numberToPieceInfo(board[pos]) | |
i++ | |
if(posInfo.piece == "") { | |
validMoves.push(pos) | |
continue | |
} | |
if(posInfo.color !== playerColor) { | |
validMoves.push(pos) | |
break | |
} else { | |
break | |
} | |
} else { | |
break | |
} | |
} | |
} | |
checkQueenDiag(1, 1) | |
checkQueenDiag(1, -1) | |
checkQueenDiag(-1, -1) | |
checkQueenDiag(-1, 1) | |
checkQueenDiag(0, 1) | |
checkQueenDiag(0, -1) | |
checkQueenDiag(1, 0) | |
checkQueenDiag(-1, 0) | |
break | |
} | |
} | |
validMoves = validMoves.filter(onlyUnique) | |
if(needCheckFix) { | |
var fixes = [] | |
for(var vmove in validMoves) { | |
if(moveFixesCheck(board, | |
{ | |
from: { | |
word: piecePos.word, | |
num: piecePos.num | |
}, | |
to: { | |
word: validMoves[vmove][0], | |
num: validMoves[vmove][1] | |
} | |
}, | |
playerColor)) { | |
log.push("Found valid move that prevents check/checkmate:") | |
log.push(piecePos.word + piecePos.num + " to " + validMoves[vmove]) | |
fixes.push(validMoves[vmove]) | |
} | |
} | |
validMoves = fixes | |
} | |
return validMoves | |
} | |
var getBoardPiece = (board, pos) => { | |
return board[pos.word + pos.num] | |
} | |
var setBoardPiece = (board, pos, piece) => { | |
board[pos.word + pos.num] = piece | |
} | |
var pieces = { | |
pawn: [ | |
[0, 0, 2, 4, 3, 0, 0], | |
[0, 0, 3, 1, 2, 0, 0], | |
[0, 0, 0, 1, 0, 0, 0], | |
[0, 0, 2, 1, 3, 0, 0], | |
[0, 2, 1, 1, 1, 3, 0], | |
], | |
rook: [ | |
[0, 4, 0, 4, 0, 4, 0], | |
[0, 4, 4, 4, 4, 4, 0], | |
[0, 0, 1, 1, 1, 0, 0], | |
[0, 0, 1, 1, 1, 0, 0], | |
[0, 2, 1, 1, 1, 3, 0], | |
], | |
knight: [ | |
[0, 2, 4, 4, 4, 0, 0], | |
[0, 1, 2, 1, 1, 0, 0], | |
[0, 5, 0, 1, 1, 0, 0], | |
[0, 0, 2, 1, 1, 0, 0], | |
[0, 2, 1, 1, 1, 3, 0], | |
], | |
bishop: [ | |
[0, 0, 0, 1, 0, 0, 0], | |
[0, 0, 3, 1, 2, 0, 0], | |
[0, 0, 0, 1, 0, 0, 0], | |
[0, 0, 2, 1, 3, 0, 0], | |
[0, 2, 1, 1, 1, 3, 0], | |
], | |
queen: [ | |
[0, 7, 7, 0, 7, 7, 0], | |
[0, 0, 3, 1, 2, 0, 0], | |
[0, 0, 0, 1, 0, 0, 0], | |
[0, 0, 2, 1, 3, 0, 0], | |
[0, 2, 1, 1, 1, 3, 0], | |
], | |
king: [ | |
[0, 7, 0, 7, 0, 7, 0], | |
[0, 0, 3, 1, 2, 0, 0], | |
[0, 0, 0, 1, 0, 0, 0], | |
[0, 0, 2, 1, 3, 0, 0], | |
[0, 2, 1, 1, 1, 3, 0], | |
] | |
} | |
drawBoard() | |
#db.r({sid:"test_chess_game"}) | |
var game = #db.f({sid:"test_chess_game"}).first() || {board:START_BOARD, white:"chess", black:"chess", move:1, log:[]} | |
game.board.c8 = EMPTY | |
game.board.d7 = EMPTY | |
game.board.d8 = EMPTY | |
game.board.d3 = WHITE_QUEEN | |
game.board.d5 = WHITE_QUEEN | |
if(a.move && a.color) { | |
// pawn promotion will be a problem due to the design | |
var op = interpretOperation(a.move) | |
var moves = getValidMoves(game.board, op.from, a.color) | |
if(moves && moves.includes(op.to.word + op.to.num)) { | |
game.log.push("Moves : " + moves) | |
var piece = getBoardPiece(game.board, op.from) | |
var pieceTo = getBoardPiece(game.board, op.to) | |
setBoardPiece(game.board, op.from, EMPTY) | |
setBoardPiece(game.board, op.to, piece) | |
// TODO add full notation here, e.g capture etc | |
// This is not how log works | |
game.log.push(game.move + `. ${op.from.word}${op.from.num} ${op.to.word}${op.to.num}`) | |
game.move++ | |
} | |
} | |
game.log.push("Is checkmate for " + (a.color == "white" ? "black" : "white") + ": " + isCheckMate(game.board, a.color == "white" ? "black" : "white")) | |
drawPiecesFromBoardInfo(game.board) | |
var logString = r.String(boardRect.x + boardRect.w + 1, 2, game.log.join("\n")) | |
var poweredBy = r.String(0, r.displaySize.y - 1, "Powered by multivac.renderer", "L") | |
r.draw(poweredBy) | |
r.draw(logString) | |
#db.us({sid:"test_chess_game"}, {$set:game}) | |
log.push("Done in: " + String(Date.now()-_START) + "ms") | |
return r.render() + "\n\nLog:\n" + game.log.join("\n") + "\n\n" + log.join("\n") | |
// play{move:"d5 d8", color:"white"} | |
// fix weird | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment