Chess
This is an implementation of chess. Most functionality is implemented, so you can play it to some extent, but the following functionality is still missing:
- Pawns can't move two steps on the first move
- Pawns can't capture en passant
- Pawns can't be turned into other pieces when reaching the opposite side of the board
- The game never ends/never checks for a winner
- The game never cheks for checks
class MyApp extends App{
pieces = [
// Top row.
{c: 0, r: 0, text: `♜`, team: `black`, type: `rook`},
{c: 1, r: 0, text: `♞`, team: `black`, type: `knight`},
{c: 2, r: 0, text: `♝`, team: `black`, type: `bishop`},
{c: 3, r: 0, text: `♛`, team: `black`, type: `queen`},
{c: 4, r: 0, text: `♚`, team: `black`, type: `king`},
{c: 5, r: 0, text: `♝`, team: `black`, type: `bishop`},
{c: 6, r: 0, text: `♞`, team: `black`, type: `knight`},
{c: 7, r: 0, text: `♜`, team: `black`, type: `rook`},
// Second row from the top.
{c: 0, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 1, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 2, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 3, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 4, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 5, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 6, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
{c: 7, r: 1, text: `♟`, team: `black`, type: `pawn`, direction: 1},
// Second row from the bottom.
{c: 0, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 1, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 2, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 3, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 4, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 5, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 6, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
{c: 7, r: 6, text: `♙`, team: `white`, type: `pawn`, direction: -1},
// Bottom row.
{c: 0, r: 7, text: `♖`, team: `white`, type: `rook`},
{c: 1, r: 7, text: `♘`, team: `white`, type: `knight`},
{c: 2, r: 7, text: `♗`, team: `white`, type: `bishop`},
{c: 3, r: 7, text: `♕`, team: `white`, type: `queen`},
{c: 4, r: 7, text: `♔`, team: `white`, type: `king`},
{c: 5, r: 7, text: `♗`, team: `white`, type: `bishop`},
{c: 6, r: 7, text: `♘`, team: `white`, type: `knight`},
{c: 7, r: 7, text: `♖`, team: `white`, type: `rook`},
]
nextTeamToMove = `white`
selectedPieceR = -1
selectedPieceC = -1
createStartPage(){
return StartPage
}
selectPiece(r, c){
a.selectedPieceR = r
a.selectedPieceC = c
}
deselectPiece(){
a.selectedPieceR = -1
a.selectedPieceC = -1
}
getSelectedPiece(){
return a.getPieceByCoordinates(
a.selectedPieceR,
a.selectedPieceC
)
}
getPieceByCoordinates(r, c){
return a.pieces.find(p => p.r == r && p.c == c)
}
moveSelectedPieceTo(r, c){
const arrivingPiece = a.getPieceByCoordinates(r, c)
if(arrivingPiece){
a.pieces = a.pieces.filter(
p => p != arrivingPiece
)
}
const pieceToMove = a.getSelectedPiece()
pieceToMove.r = r
pieceToMove.c = c
a.nextTeamToMove = (a.nextTeamToMove == `white` ? `black` : `white`)
a.selectedPieceR = -1
a.selectedPieceC = -1
}
getMovableCoordinates(piece){
if(!piece){
return []
}
if(piece.type == `pawn`){
return a.getPawnMovableCoordinates(piece)
}
if(piece.type == `king`){
return a.getKingMovableCoordinates(piece)
}
if(piece.type == `rook`){
return a.getRookMovableCoordinates(piece)
}
if(piece.type == `bishop`){
return a.getBishopMovableCoordinates(piece)
}
if(piece.type == `queen`){
return a.getQueenMovableCoordinates(piece)
}
if(piece.type == `knight`){
return a.getKnightMovableCoordinates(piece)
}
return []
}
getPawnMovableCoordinates(pawnPiece){
const moveableCoordinates = []
// Check one step forward towards the left side of the board.
if(pawnPiece.c != 0){
const arrivingR = pawnPiece.r + pawnPiece.direction
const arrivingC = pawnPiece.c - 1
const arrivingPiece = a.getPieceByCoordinates(arrivingR, arrivingC)
if(arrivingPiece && arrivingPiece.team != pawnPiece.team){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
}
// Check one step forward.
const arrivingR = pawnPiece.r + pawnPiece.direction
const arrivingC = pawnPiece.c
const arrivingPiece = a.getPieceByCoordinates(arrivingR, arrivingC)
if(!arrivingPiece){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
// Check one step forward towards the right side of the board.
if(pawnPiece.c != 7){
const arrivingR = pawnPiece.r + pawnPiece.direction
const arrivingC = pawnPiece.c + 1
const arrivingPiece = a.getPieceByCoordinates(arrivingR, arrivingC)
if(arrivingPiece && arrivingPiece.team != pawnPiece.team){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
}
return moveableCoordinates
}
getKingMovableCoordinates(kingPiece){
const moveableCoordinates = []
function checkSquare(dr, dc){
const arrivingR = kingPiece.r + dr
const arrivingC = kingPiece.c + dc
if(0 <= arrivingR && arrivingR <= 7){
if(0 <= arrivingC && arrivingC <= 7){
const arrivingPiece = a.getPieceByCoordinates(arrivingR, arrivingC)
if(!arrivingPiece || arrivingPiece.team != kingPiece.team){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
}
}
}
checkSquare(-1, -1)
checkSquare(-1, 0)
checkSquare(-1, 1)
checkSquare(0, -1)
checkSquare(0, 1)
checkSquare(1, -1)
checkSquare(1, 0)
checkSquare(1, 1)
return moveableCoordinates
}
getRookMovableCoordinates(rookPiece){
const moveableCoordinates = []
function checkDirection(dr, dc){
let arrivingR = rookPiece.r
let arrivingC = rookPiece.c
while(
(0 <= arrivingR && arrivingR <= 7) &&
(0 <= arrivingC && arrivingC <= 7)
){
arrivingR += dr
arrivingC += dc
const arrivingPiece = a.getPieceByCoordinates(
arrivingR,
arrivingC,
)
if(!arrivingPiece || arrivingPiece.team != rookPiece.team){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
// As soon as we encounter a piece, we can't jump over it,
// so stop the loop by returning from the function here.
if(arrivingPiece){
return
}
}
}
checkDirection(0, 1)
checkDirection(0, -1)
checkDirection(1, 0)
checkDirection(-1, 0)
return moveableCoordinates
}
getBishopMovableCoordinates(bishopPiece){
const moveableCoordinates = []
function checkDirection(dr, dc){
let arrivingR = bishopPiece.r
let arrivingC = bishopPiece.c
while(
(0 <= arrivingR && arrivingR <= 7) &&
(0 <= arrivingC && arrivingC <= 7)
){
arrivingR += dr
arrivingC += dc
const arrivingPiece = a.getPieceByCoordinates(
arrivingR,
arrivingC,
)
if(!arrivingPiece || arrivingPiece.team != bishopPiece.team){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
// As soon as we encounter a piece, we can't jump over it,
// so stop the loop by returning from the function here.
if(arrivingPiece){
return
}
}
}
checkDirection(1, 1)
checkDirection(1, -1)
checkDirection(-1, 1)
checkDirection(-1, -1)
return moveableCoordinates
}
getQueenMovableCoordinates(queenPiece){
return a.getRookMovableCoordinates(queenPiece).concat(
a.getBishopMovableCoordinates(queenPiece),
)
}
getKnightMovableCoordinates(knightPiece){
const moveableCoordinates = []
function checkSquare(dr, dc){
const arrivingR = knightPiece.r + dr
const arrivingC = knightPiece.c + dc
if(
(0 <= arrivingR && arrivingR <= 7) &&
(0 <= arrivingC && arrivingC <= 7)
){
const arrivingPiece = a.getPieceByCoordinates(
arrivingR,
arrivingC,
)
if(!arrivingPiece || arrivingPiece.team != knightPiece.team){
moveableCoordinates.push({
r: arrivingR,
c: arrivingC,
})
}
}
}
checkSquare(-2, -1)
checkSquare(-2, 1)
checkSquare(-1, -2)
checkSquare(-1, 2)
checkSquare(1 , -2)
checkSquare(1 , 2)
checkSquare(2 , -1)
checkSquare(2 , 1)
return moveableCoordinates
}
}
class StartPage extends Page{
createCellComponents(){
const selectedPiece = a.getSelectedPiece()
const possibleMoveToCoordinates = a.getMovableCoordinates(selectedPiece)
const rows = []
for(let r=0; r<8; r++){
const columns = [
Text.text(`${8-r}`).grow(1).border(0.5, `black`)
]
for(let c=0; c<8; c++){
const backgroundColor = (
r % 2 == c % 2 ?
`yellow` :
`silver`
)
const piece = a.getPieceByCoordinates(r, c)
const canSelectedMoveToCurrentSquare = (
selectedPiece && possibleMoveToCoordinates.some(
coord => coord.r == r && coord.c == c,
)
)
let square = null
if(selectedPiece && selectedPiece == piece){
square = Button.text(piece.text).onClick(a.deselectPiece)
}else if(canSelectedMoveToCurrentSquare){
square = Button.text(piece?.text ?? ` `).onClick(a.moveSelectedPieceTo, r, c)
}else if(!piece){
square = Text.text(` `)
}else if(!selectedPiece && piece.team == a.nextTeamToMove){
square = Button.text(piece?.text ?? ` `).onClick(a.selectPiece, r, c)
}else{
square = Text.text(piece.text)
}
columns.push(
Box
.grow(1)
.border(0.5, `black`)
.backgroundColor(backgroundColor)
.aspectRatio(1, 1)
.child(square),
)
}
rows.push(
Columns.grow(1).children(
...columns
)
)
}
return Rows.border(0.5, `black`).children(
rows,
Columns.grow(1).children([
Space.grow(1).border(0.5, `black`),
Text.text(`A`).grow(1).border(0.5, `black`),
Text.text(`B`).grow(1).border(0.5, `black`),
Text.text(`C`).grow(1).border(0.5, `black`),
Text.text(`D`).grow(1).border(0.5, `black`),
Text.text(`E`).grow(1).border(0.5, `black`),
Text.text(`F`).grow(1).border(0.5, `black`),
Text.text(`G`).grow(1).border(0.5, `black`),
Text.text(`H`).grow(1).border(0.5, `black`),
])
)
}
createGui(){
return Rows.padding(1).children(
Text.text(`Chess`),
Box.grow(1).aspectRatio(1, 1).child(
p.createCellComponents(),
),
Text.text(`Good luck!`),
)
}
}