Skip to content

Instantly share code, notes, and snippets.

@edg-l
Created October 4, 2018 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edg-l/d1f52f7883792e642442fa8f5d439847 to your computer and use it in GitHub Desktop.
Save edg-l/d1f52f7883792e642442fa8f5d439847 to your computer and use it in GitHub Desktop.
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