removed accidentally created submodule, broke everything up into JS modules, put CSS in its own file. create game and update state from FEN-formatted strings. also implemented serialization to FEN

This commit is contained in:
2022-02-07 13:51:38 +01:00
parent 6d1cac8231
commit 3f847b2eee
6 changed files with 231 additions and 66 deletions

49
app.js Normal file
View File

@@ -0,0 +1,49 @@
import { LETTER_PIECE_NAME_LOOKUP, STARTING_FEN } from './constants.js'
import { getGameStateFromFEN, toFEN } from './fen.js'
const GREEN = "#779558"
const BEIGE = "#eeedd3"
const board = document.getElementById("board")
let gameState = {}
const renderBoard = (fromPerspective = "white") => {
// adapted from https://codepen.io/Staghouse/pen/OzpVya
let lastColor = BEIGE
let files = Array.from("abcdefgh")
let ranks = Array.from("12345678")
if (fromPerspective === "white") {
ranks.reverse()
} else {
files.reverse()
}
ranks.forEach(num => {
files.forEach((char, charIndex) => {
if (charIndex % 7 || charIndex > 0) {
lastColor = lastColor === BEIGE ? GREEN : BEIGE
}
const box = document.createElement("div")
box.classList.add("box")
box.id = `${char}${num}`
box.style.backgroundColor = lastColor
board.appendChild(box)
})
})
}
const initializeGameState = () => {
gameState = getGameStateFromFEN(STARTING_FEN)
}
const renderPieces = () => null
const initializeGame = () => {
renderBoard()
initializeGameState()
renderPieces()
console.log(gameState)
console.log(toFEN(gameState))
console.log(toFEN(gameState) === STARTING_FEN)
console.log(STARTING_FEN)
}
document.addEventListener("DOMContentLoaded", initializeGame)

20
constants.js Normal file
View File

@@ -0,0 +1,20 @@
export const LETTER_PIECE_NAME_LOOKUP = {}
export const PIECE_NAME_LETTER_LOOKUP = {}
for (const [letter, name] of Object.entries({
r: 'rook',
b: 'bishop',
q: 'queen',
k: 'king',
p: 'pawn',
n: 'knight',
})) {
LETTER_PIECE_NAME_LOOKUP[letter] = `black-${name}`
LETTER_PIECE_NAME_LOOKUP[letter.toUpperCase()] = `white-${name}`
PIECE_NAME_LETTER_LOOKUP[`black-${name}`] = letter
PIECE_NAME_LETTER_LOOKUP[`white-${name}`] = letter.toUpperCase()
}
export const files = Array.from("abcdefgh")
export const STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"

131
fen.js Normal file
View File

@@ -0,0 +1,131 @@
import { LETTER_PIECE_NAME_LOOKUP, PIECE_NAME_LETTER_LOOKUP, files } from './constants.js'
const parseCastlingAvailability = castlingAvailability => {
let castlingAvailabilityObj = {whiteKing: false, whiteQueen: false, blackKing: false, blackQueen: false}
if (castlingAvailability !== "-") {
for (const char of castlingAvailability) {
if (char === "K") {
castlingAvailabilityObj.whiteKing = true
} else if (char === "Q") {
castlingAvailabilityObj.whiteQueen = true
} else if (char === "k") {
castlingAvailabilityObj.blackKing = true
} else if (char === "q") {
castlingAvailabilityObj.blackQueen = true
}
}
}
return castlingAvailabilityObj
}
const castlingAvailabilityToFEN = castlingAvailabilityObj => {
let FEN = ""
if (castlingAvailabilityObj.whiteKing) {
FEN += "K"
}
if (castlingAvailabilityObj.whiteQueen) {
FEN += "Q"
}
if (castlingAvailabilityObj.blackKing) {
FEN += "k"
}
if (castlingAvailabilityObj.blackQueen) {
FEN += "q"
}
FEN = FEN || "-"
return FEN
}
const parsePositions = positions => {
let board = {}
const ranks = positions.split('/')
ranks.forEach((tokens, rankIndex) => {
rankIndex = Math.abs(rankIndex - 7)
let rank = rankIndex + 1
Array.from(tokens).forEach((token, fileIndex) => {
let parsed = parseInt(token)
if (isNaN(parsed)) {
let file = files[fileIndex]
board[`${file}${rank}`] = LETTER_PIECE_NAME_LOOKUP[token]
}
})
})
return board
}
const boardToFEN = board => {
let FEN = ""
let blanks = 0
let files = Array.from("abcdefgh")
let ranks = Array.from("12345678").reverse()
ranks.forEach((rank, rankIndex) => {
if (rankIndex) {
if (blanks) {
FEN += blanks
blanks = 0
}
FEN += "/"
}
files.forEach((file, charIndex) => {
let cell = `${file}${rank}`
if (board.hasOwnProperty(cell)) {
if (blanks) {
// it's insane this works, JS casts `blanks` to a String
FEN += blanks
blanks = 0
}
FEN += PIECE_NAME_LETTER_LOOKUP[board[cell]]
} else {
blanks++
}
})
})
return FEN
}
export const toFEN = gameState => {
let {
board,
whoseTurn,
castlingAvailability,
enPassantTargetSquare,
halfMoveClock,
fullMoveNum
} = gameState
enPassantTargetSquare = !enPassantTargetSquare ? "-" : enPassantTargetSquare
return [
boardToFEN(board),
whoseTurn[0],
castlingAvailabilityToFEN(castlingAvailability),
enPassantTargetSquare,
halfMoveClock,
fullMoveNum,
].join(" ")
}
export const getGameStateFromFEN = FEN => {
// https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
let [
positions,
whoseTurn,
castlingAvailability,
enPassantTargetSquare,
halfMoveClock,
fullMoveNum
] = FEN.split(" ")
whoseTurn = whoseTurn === "w" ? "white" : "black"
enPassantTargetSquare = enPassantTargetSquare === "-" ? null : enPassantTargetSquare
const castlingAvailabilityObj = parseCastlingAvailability(castlingAvailability)
fullMoveNum = parseInt(fullMoveNum)
halfMoveClock = parseInt(halfMoveClock)
return {
board: parsePositions(positions),
whoseTurn,
fullMoveNum,
halfMoveClock,
enPassantTargetSquare,
castlingAvailability: castlingAvailabilityObj,
}
}

View File

@@ -1,4 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,45)">
<path
@@ -20,5 +19,4 @@
d="M 11.5,30 C 17,27 27,27 32.5,30 M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5 M 11.5,37 C 17,34 27,34 32.5,37"
style="fill:none; stroke:#ffffff;" />
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -3,70 +3,11 @@
<head>
<meta charset="utf-8">
<title></title>
<style>
#board {
margin: auto;
padding-top: 4vw;
width: 50vw;
height: 50vh;
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: auto;
}
.box::before {
content: "";
display: block;
padding-top: 100%;
}
body {
padding: 0;
margin: 0;
background-color: #212121;
}
body .grid {
max-width: 570px;
margin: 30px auto;
box-sizing: border-box;
border: 10px solid #2a351f;
}
</style>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
</head>
<body onload="renderBoard()">
<main id="board">
</main>
<script>
const GREEN = "#779558"
const BEIGE = "#eeedd3"
const board = document.getElementById("board")
// const state = {
// a1: "
// }
const renderBoard = (fromPerspective = "white") => {
// adapted from https://codepen.io/Staghouse/pen/OzpVya
let lastColor = BEIGE
let files = Array.from("abcdefgh")
let ranks = Array.from("12345678")
if (fromPerspective === "white") {
ranks.reverse()
} else {
files.reverse()
}
ranks.forEach(char => {
files.forEach((num, numIndex) => {
if (numIndex % 7 || numIndex > 0) {
lastColor = lastColor === BEIGE ? GREEN : BEIGE
}
const box = document.createElement("div")
box.classList.add("box")
box.id = `${num}${char}`
box.style.backgroundColor = lastColor
board.appendChild(box)
})
})
}
</script>
<body>
<main id="board"></main>
<script type="module" src="app.js"></script>
</body>
</html>

26
styles.css Normal file
View File

@@ -0,0 +1,26 @@
#board {
margin: auto;
padding-top: 4vw;
width: 50vw;
height: 50vh;
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: auto;
}
.box::before {
content: "";
display: block;
padding-top: 100%;
}
body {
padding: 0;
margin: 0;
background-color: #212121;
}
body .grid {
max-width: 570px;
margin: 30px auto;
box-sizing: border-box;
border: 10px solid #2a351f;
}