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">
|
||||
<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 |
67
index.html
67
index.html
@@ -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
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