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:
49
app.js
Normal file
49
app.js
Normal 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
20
constants.js
Normal 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
131
fen.js
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
<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)">
|
<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
|
<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"
|
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;" />
|
style="fill:none; stroke:#ffffff;" />
|
||||||
</g>
|
</g>
|
||||||
|
</svg>
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
67
index.html
67
index.html
@@ -3,70 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title></title>
|
<title></title>
|
||||||
<style>
|
<link rel="stylesheet" href="styles.css">
|
||||||
#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="icon" href="favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="favicon.ico" type="image/x-icon" />
|
||||||
</head>
|
</head>
|
||||||
<body onload="renderBoard()">
|
<body>
|
||||||
<main id="board">
|
<main id="board"></main>
|
||||||
</main>
|
<script type="module" src="app.js"></script>
|
||||||
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
26
styles.css
Normal file
26
styles.css
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user