diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fad6fd4..9e743a2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,15 +28,6 @@ jobs: run: npm run build --if-present working-directory: ./middlewareNode - # chessClient - - name: Install dependencies (chessClient) - run: npm ci - working-directory: ./chessClient - - - name: Lint chessClient - run: npm run lint - working-directory: ./chessClient - # chessServer - name: Install dependencies (chessServer) run: npm ci @@ -83,7 +74,6 @@ jobs: }, urls: { middlewareURL: '${{ secrets.MIDDLEWARE_URL }}', - chessClientURL: '${{ secrets.CHESS_CLIENT_URL }}', stockFishURL: '${{ secrets.STOCKFISH_URL }}', chessServer: '${{ secrets.CHESS_SERVER }}', }, diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d15ac516..00000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: node_js -node_js: -- "14.9.0" -dist: trusty -sudo: required - -branches: - only: - - master - - Travis_Fix - -before_script: -- bash create_travis_envs.sh -- cd YStemAndChess -- npm i - -script: -- npm run test-headless -- ng build --prod diff --git a/README.md b/README.md index 0bbabced..7f2c6eb7 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,67 @@ # Welcome to the Y STEM and Chess Project -This is an educational platform that combines chess instruction with STEM learning, aimed at supporting socially and economically underserved students. The platform helps students who benefit from alternative learning approaches by providing chess tutoring alongside mathematics, computer science, and mentoring. - ---- +This is an educational platform combining chess instruction with STEM learning, aimed at supporting socially and economically underserved students. It helps learners who benefit from alternative approaches by providing chess tutoring alongside mathematics, computer science, and mentoring. The platform is built with React and Node.js and integrates the Stockfish chess engine for gameplay. ## Development Environment Setup -To run the platform, you’ll need **Node.js v18**, **nodemon**, and **Apache** installed. - -### NodeJS +To run the platform, you’ll need **Node.js v18** installed. **Nodemon** is optional and can be used to automatically restart servers when code changes, which is convenient during development. -This project requires **Node.js v18.20.8**. - -We recommend using a version manager so everyone runs the same Node.js version consistently. +### Node.js --- -### Option 1: Using [Volta](https://volta.sh) (recommended) +This project requires **Node.js v18.20.8**. Using a version manager ensures consistent Node versions across contributors. + +#### Option 1: Using [Volta](https://volta.sh) (recommended) Volta automatically uses the Node version pinned in this project’s `package.json`. **Install Volta:** -- **Linux / macOS:** - ```bash - curl https://get.volta.sh | bash - source ~/.bashrc # or ~/.zshrc - ``` -- **Windows:** -Download and run the installer from [volta.sh](https://volta.sh) - -Now when you cd into this project, Volta will automatically install and use **Node.js v18.20.8**. - -### Option 2: Using [nvm](https://github.com/nvm-sh/nvm) - -If you prefer nvm: - -- **Linux / macOS:** - ```bash - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash - source ~/.nvm/nvm.sh - ``` - Then: - ```bash - nvm install 18.20.8 - nvm use 18.20.8 - ``` - (Optional) Set it as default: - ```bash - nvm alias default 18.20.8 - ``` -- **Windows:** - Use [nvm-windows](https://github.com/coreybutler/nvm-windows): - - Download and run the installer from the [releases page](https://github.com/coreybutler/nvm-windows/releases). - Then install and use Node: - ```powershell - nvm install 18.20.8 - nvm use 18.20.8 - ``` - -### Nodemon - -Nodemon automatically restarts your server whenever code changes are detected. - -**Linux/macOS:** +* **Linux / macOS** ```bash -sudo npm install -g nodemon +curl https://get.volta.sh | bash +source ~/.bashrc # or ~/.zshrc ``` -**Windows:** - -```bash -npm install -g nodemon -``` +* **Windows** + Download and run the installer from [volta.sh](https://volta.sh). ---- +Volta will automatically install and use **Node.js v18.20.8** when you cd into the project. -### Apache +#### Option 2: Using [nvm](https://github.com/nvm-sh/nvm) -Apache is used as a web server to serve the chess client interface. +If you prefer nvm: -**Linux:** +* **Linux / macOS** ```bash -sudo apt update && sudo apt install apache2 +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +source ~/.nvm/nvm.sh +nvm install 18.20.8 +nvm use 18.20.8 +nvm alias default 18.20.8 # optional ``` -**macOS:** +* **Windows** + Use [nvm-windows](https://github.com/coreybutler/nvm-windows/releases). -```bash -brew install httpd +```powershell +nvm install 18.20.8 +nvm use 18.20.8 ``` -**Windows:** -[Apache Installation Guide](https://httpd.apache.org/docs/2.4/platform/windows.html) +### Nodemon (Optional) --- +Nodemon can be installed globally to automatically restart servers when code changes: + +```bash +npm install -g nodemon +``` + ## Getting Started ### 1. Clone the Repository @@ -110,45 +73,33 @@ cd react ### 2. Environment Setup -Running the following script creates all environment files needed for the platform services: +A `default.json` file containing environment variables will be provided to contributors and should be placed in `middlewareNode/config`. -**Linux/macOS or Windows (Git Bash):** -```bash -sh create_dev_envs.sh -``` - ---- +## Running Each Service -## Setting Up Each Service +Each service runs independently in its own terminal window. Start the Middleware API first, then the other services in any order. -Each service runs independently and requires its own terminal window. Start the Middleware API first, then the other services in any order. - -#### Middleware API (Backend) +### Middleware API (Backend) Handles user authentication, database operations, and coordinates other services. ```bash cd middlewareNode -npm install # Install backend dependencies -npm start # Start the API server (runs from src/server.js) +npm install +npm start ``` -The server typically runs on port 8000. You should see "MongoDB Connected..." when it starts successfully. - -> **Note**: After modularization, the entry point is now `src/server.js` - -**If you intend to test the mentor and student login pages, you may use the following usernames and passwords respectively:** - -mentor 123123123 +The server typically runs on port 8000. You should see `"MongoDB Connected..."` when it starts successfully. -student 123123123 +The following credentials can be used for testing mentor and student accounts: ---- +* **Mentor:** mentor / 123123123 +* **Student:** student / 123123123 -#### Main React Application (Frontend) +### Main React Application (Frontend) -Primary interface for students and mentors: +Primary interface for students and mentors. ```bash cd react-ystemandchess @@ -156,70 +107,31 @@ npm install npm start ``` -The frontend runs on `http://localhost:3000` (or the next available port if 3000 is occupied). - ---- +Once started, the React application is accessible at `http://localhost:3000` (or the next available port if 3000 is occupied). -#### Chess Game Server +### Chess Game Server -Manages chess game logic, validates moves, and handles real-time gameplay: +Manages chess game logic, validates moves, and handles real-time gameplay. ```bash cd chessServer npm install -npm start # Starts from src/index.js +npm start ``` -Defaults to port 3000 (or the next available port if taken). - -> **Note**: After modularization, the entry point is now `src/index.js` +The server runs on port 3001. ---- - -#### Chess Engine Server +### Chess Engine Server -Integrates Stockfish for AI opponents and move analysis: +Integrates Stockfish for AI opponents and move analysis. ```bash cd stockfishServer npm install -npm start # Starts from src/index.js -``` - -This service usually runs on port 8080. - -> **Note**: After modularization, the entry point is now `src/index.js` - ---- - -#### Chess Client (Testing Interface) - -```bash -cd chessClient -npm install -``` - -Run the http server on port 80 using: `npx http-server -p 80` to ensure that the chess board frame loads during any session. -You can use the **Live Server extension in VS Code** to open the HTML files for local testing of the chess board: - -* **Board only:** Right-click `index.html` → "Open with Live Server" -* **Archived files:** `archive/parent.html` and `archive/both.html` contain legacy testing interfaces - -> **Note**: Old HTML test files have been moved to `archive/` directory after modularization - ---- - -#### React Chess Component (Optional) - -Modern React-based chess component for development/testing: - -```bash -cd chess-client-react-refactor -npm install -npm start +npm start ``` -Open `src/app.js` to ensure debug mode is set to `true`. +The server runs on port 8080. --- @@ -228,7 +140,7 @@ Open `src/app.js` to ensure debug mode is set to `true`. 1. Create a new branch with a descriptive name: ```bash -git checkout -b my-branch-name +git checkout -b branch-name ``` 2. Test thoroughly to ensure all services work together. @@ -237,30 +149,4 @@ git checkout -b my-branch-name --- -## Project Structure - -This project has been recently modularized for better organization and maintainability. Key structural changes: - -- **All Node.js services** now have their code in a `src/` directory -- **React app** uses a feature-based architecture with: - - `components/` - Reusable UI components - - `features/` - Feature modules (auth, lessons, student, mentor, etc.) - - `core/` - Core infrastructure (services, types, utils) - - `assets/` - Static assets and images -- **Configuration files** are organized in the `config/` directory -- **Kubernetes deployments** are in the `yaml/` directory - ---- - -## Docker Deployment - -To run all services using Docker: - -```bash -cd config -docker-compose up -``` - ---- - -You're all set! Happy coding and thank you for contributing to educational equity! 🎯♟️ \ No newline at end of file +You're all set! Happy coding, and thank you for contributing to educational equity! 🎯♟️ \ No newline at end of file diff --git a/angular-ystemandchess-old/.gitignore b/angular-ystemandchess-old/.gitignore index b10869cb..92fa0b92 100644 --- a/angular-ystemandchess-old/.gitignore +++ b/angular-ystemandchess-old/.gitignore @@ -1,3 +1,4 @@ # Track the changes that were made changes.md dist +environment.* diff --git a/chessClient/.gitignore b/chessClient/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/chessClient/Dockerfile b/chessClient/Dockerfile deleted file mode 100644 index cab8ae8c..00000000 --- a/chessClient/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM nginx - -COPY ./ /usr/share/nginx/html - -EXPOSE 4200 \ No newline at end of file diff --git a/chessClient/README.md b/chessClient/README.md deleted file mode 100644 index f683d041..00000000 --- a/chessClient/README.md +++ /dev/null @@ -1 +0,0 @@ -This needs to be run in an apache server. diff --git a/chessClient/archive/both.html b/chessClient/archive/both.html deleted file mode 100644 index 0a55f2b9..00000000 --- a/chessClient/archive/both.html +++ /dev/null @@ -1,144 +0,0 @@ - - -
- - -This is the parent page. It will receive messages from the embedded child app (JavaScript).
- - - - - - - -
-
-
-
-
\ No newline at end of file
diff --git a/chessClient/index.js b/chessClient/index.js
deleted file mode 100644
index e2659a23..00000000
--- a/chessClient/index.js
+++ /dev/null
@@ -1,1118 +0,0 @@
-// Global state flags for lesson management
-let flag = false
-let lessonFlag = false
-let isLesson = false
-let lessonStarted = false
-let lessonBoard = ''
-let lessonEnd = ''
-let endSquare = ''
-let previousEndSquare = ''
-
-// Chessboard UI configuration
-var squareClass = 'square-55d63'
-var $board = $('#myBoard')
-var board = null
-var currentState = new Chess()
-var whiteSquareGrey = '#eebe7bf7'
-var blackSquareGrey = '#ae5716d6'
-
-var serverStartNotified = false
-
-// Cross-browser event listener compatibility
-var eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent'
-var eventer = window[eventMethod]
-var messageEvent = eventMethod == 'attachEvent' ? 'onmessage' : 'message'
-
-// User identification
-var mentor = ''
-var student = ''
-var role = 'student'
-
-var playerColor
-
-// Free move mode allows moving pieces freely without restrictions
-var freemoveFlag = false
-
-// Mouse tracking for showing opponent's cursor
-var myMouseX
-var myMouseY
-const mouseImage = 'img/cursor.png'
-
-var opponentMouseX = 0
-var opponentMouseY = 0
-
-// Puzzle-specific variables
-var nextPuzzleMove = []
-var isPuzzle = false
-
-// Move highlighting state
-var highlightFrom = ''
-var highlightTo = ''
-
-// Listen for mouse position change
-document.addEventListener('mousemove', (event) => {
- myMouseX = event.clientX
- myMouseY = event.clientY
-
- if (mentor && student) {
- sendMouseXY()
- }
-})
-
-/**
- * Updates the visual position of opponent's cursor on screen
- */
-function updateOpponentMouseXY () {
- const img = document.getElementById('cursor')
-
- // Set absolute position values (top and left in pixels)
- img.style.top = `${opponentMouseY}px`
- img.style.left = `${opponentMouseX}px`
-}
-
-// References to chess piece images
-const chessPieceFolder = 'img/chesspieces/wikipedia/'
-
-const chessImages = {
- bB: 'bB.png', // Black Bishop
- bK: 'bK.png', // Black King
- bN: 'bN.png', // Black Knight
- bP: 'bP.png', // Black Pawn
- bQ: 'bQ.png', // Black Queen
- bR: 'bR.png', // Black Rook
- wB: 'wB.png', // White Bishop
- wK: 'wK.png', // White King
- wN: 'wN.png', // White Knight
- wP: 'wP.png', // White Pawn
- wQ: 'wQ.png', // White Queen
- wR: 'wR.png' // White Rook
-}
-
-/**
- * Returns the full path to a chess piece image
- * @param {string} peice - Piece identifier (e.g., 'wP', 'bK')
- */
-function getChessPieceImage (peice) {
- chessString = chessPieceFolder + chessImages[peice]
- return chessString
-}
-
-// Connect to chess server via Socket.IO
-const socket = io('http://localhost:3001')
-
-// Default starting FEN position
-let defaultFEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'
-
-// Notify parent window that client is ready
-letParentKnow()
-
-/**
- * Removes grey highlighting from all squares
- */
-function removeGreySquares () {
- $('#myBoard .square-55d63').css('background', '')
- $('#myBoard .square-55d63').css('background-image', '')
- $('#myBoard .square-55d63').css('background-position', '')
-}
-
-/**
- * Highlights a square with grey overlay and dot indicator
- * @param {string} square - Square notation (e.g., 'e4')
- */
-function greySquare (square) {
- var $square = $('#myBoard .square-' + square)
-
- var background = whiteSquareGrey
- if ($square.hasClass('black-3c85d')) {
- background = blackSquareGrey
- }
-
- $square.css('background', background)
- $square.css('background-position', 'center')
- $square.css('background-image', "url('img/chesspieces/wikipedia/dot.png')")
-}
-
-/**
- * Initiates a new chess game with the server
- */
-function sendNewGame () {
- console.log('starting new game with server')
- var data = {'mentor': mentor, 'student': student, 'role': role}
- console.log(data)
- socket.emit('newgame', JSON.stringify(data))
-}
-
-/**
- * Initiates a new puzzle session with the server
- */
-function sendNewPuzzle () {
- console.log('starting new puzzle with server')
- var data = {'mentor': mentor, 'student': student, 'role': role}
- console.log(data)
- socket.emit('newPuzzle', JSON.stringify(data))
-}
-
-/**
- * Updates the board state on the server with a new FEN string
- * @param {string} fen - FEN notation of the new board state
- */
-function sendSetState (fen) {
- console.log('setting a new board state')
- var data = {'state': fen}
- console.log(data)
- socket.emit('setstate', JSON.stringify(data))
-}
-
-/**
- * Updates the board state and player color (primarily for puzzles)
- * @param {string} fen - FEN notation of the new board state
- * @param {string} color - Player color ('white' or 'black')
- * @param {string} hints - Puzzle hints to display
- */
-function sendSetStateColor (fen, color, hints) {
- console.log('setting a new board state')
- var data = {'state': fen, 'color': color, 'hints': hints}
- console.log(data)
- socket.emit('setstateColor', JSON.stringify(data))
-}
-
-/**
- * Sends a move to the server for validation and broadcast
- * @param {string} from - Source square
- * @param {string} to - Target square
- */
-function sendMove (from, to) {
- console.log('sending move to server')
- var data = {'mentor': mentor, 'student': student, 'role': role, 'from': from, 'to': to}
- console.log(data)
- socket.emit('move', JSON.stringify(data))
-}
-
-/**
- * Notifies the server to end the current game
- */
-function sendEndGame () {
- console.log('sending end game to server')
- var data = {'mentor': mentor, 'student': student, 'role': role}
- console.log(data)
- socket.emit('endgame', JSON.stringify(data))
-}
-
-/**
- * Broadcasts mouse position to opponent
- */
-function sendMouseXY () {
- let data = {'x': myMouseX, 'y': myMouseY, 'mentor': mentor, 'student': student}
- socket.emit('mousexy', JSON.stringify(data))
-}
-
-/**
- * Broadcasts the last move for highlighting
- * @param {string} from - Source square
- * @param {string} to - Target square
- */
-function sendLastMove (from, to) {
- let data = {'from': from, 'to': to, 'mentor': mentor, 'student': student}
- socket.emit('lastmove', JSON.stringify(data))
-}
-
-/**
- * Sends highlight command to opponent
- * @param {string} from - Source square
- * @param {string} to - Target square
- */
-function sendHighLight (from, to) {
- console.log('sending highlihgt')
- let data = {'from': from, 'to': to, 'mentor': mentor, 'student': student}
- socket.emit('highlight', JSON.stringify(data))
-}
-
-/**
- * Requests the server to undo the last move
- */
-function sendUndo () {
- console.log('sending undo to server')
- var data = {'mentor': mentor, 'student': student, 'role': role}
- console.log(data)
- socket.emit('undo', JSON.stringify(data))
-}
-
-/**
- * Requests removal of grey square highlights
- */
-function sendRemoveGrey () {
- var data = {'mentor': mentor, 'student': student}
- socket.emit('removegrey', JSON.stringify(data))
-}
-
-/**
- * Requests adding grey highlight to a square
- * @param {string} to - Target square to highlight
- */
-function sendGreySquare (to) {
- var data = {'mentor': mentor, 'student': student, 'to': to}
- socket.emit('addgrey', JSON.stringify(data))
-}
-
-/**
- * Notifies opponent that a piece is being dragged
- * @param {string} piece - Piece identifier being dragged
- */
-function sendPieceDrag (piece) {
- console.log('sending drag')
- var data = {'mentor': mentor, 'student': student, 'piece': piece}
- socket.emit('piecedrag', JSON.stringify(data))
-}
-
-/**
- * Notifies opponent that a piece drag has ended
- */
-function sendPieceDrop () {
- console.log('sending drop')
- var data = {'mentor': mentor, 'student': student}
- socket.emit('piecedrop', JSON.stringify(data))
-}
-
-/**
- * Broadcasts a simple text message
- * @param {string} message - Message to send
- */
-function sendMessage (message) {
- console.log('sending message:', message)
- var data = {'message': message}
- socket.emit('message', JSON.stringify(data))
-}
-
-// notify front end that their user has created a new game/puzzle as host
-socket.on('host', () => {
- parent.postMessage('host', '*')
- isPuzzle = true
-})
-
-// notify front end that their user has joined an existing new game/puzzle as guest
-socket.on('guest', () => {
- parent.postMessage('guest', '*')
- isPuzzle = true
-})
-
-socket.on('message', (msg) => {
- parsedMsg = JSON.parse(msg)
- parent.postMessage(parsedMsg.message, '*')
-})
-
-// Handle boardstate message from the client
-socket.on('boardstate', (msg) => {
- parsedMsg = JSON.parse(msg)
-
- // if served as a client for a puzzle host (a guest does not store nextPuzzleMove for simplicity)
- if (nextPuzzleMove.length == 2) {
- // for a puzzle host, 'boardstate' message is either initiated by host itself or its guest
-
- // if initiated by host itself, ignore the message
- if (parsedMsg.boardState == currentState.fen()) return
- // else 'boardstate' message is initiated by puzzle guest
-
- // the expected puzzle moves
- var source = nextPuzzleMove[0]
- var target = nextPuzzleMove[1]
-
- // the expected chess board based on expected moves
- const testState = new Chess(currentState.fen())
- testState.move({ from: source, to: target })
-
- // new board state is different from expected board, so client has made an incorrect move
- if (testState.fen() != parsedMsg.boardState) {
- // snap back, reset puzzle to current state
- sendSetState(currentState.fen())
- highlightMove(highlightFrom, highlightTo, 'lastmove')
- } else {
- // else client has made the expected move
- nextPuzzleMove = [] // clear next moves
- currentState = new Chess(parsedMsg.boardState) // rearrange board according to guest move
- board.position(currentState.fen())
- sendToParent( // notify front end that a move has been made
- JSON.stringify({
- from: source,
- to: target
- })
- )
- }
- return
- }
-
- // update state of chess board
- console.log(currentState)
- console.log(currentState.fen())
- currentState = new Chess(parsedMsg.boardState)
- console.log('received board state', parsedMsg)
-
- // setting player color
- if (parsedMsg.color) {
- // setting player color for turn keeping
- playerColor = parsedMsg.color
- console.log(playerColor)
-
- // setting chess board orientation
- config.orientation = parsedMsg.color
- board = Chessboard('myBoard', config)
- parent.postMessage('new game received', '*')
- }
-
- // update visuals of chessboard
- board.position(currentState.fen())
-})
-
-socket.on('piecedrag', (msg) => {
- console.log('recieved piece drag')
- parsedMsg = JSON.parse(msg)
-
- // Change the image of the cursor back to default
- cursor = document.getElementById('cursor')
- pieceImage = getChessPieceImage(parsedMsg.piece)
-
- console.log(pieceImage)
- console.log(parsedMsg.piece)
-
- cursor.src = pieceImage
-})
-
-socket.on('color', (msg) => {
- console.log('received color', msg)
- parsedMsg = JSON.parse(msg)
- playerColor = parsedMsg.color
-})
-
-socket.on('piecedrop', () => {
- console.log('recieved piece drop')
-
- // Change the image of the cursor back to default
- cursor = document.getElementById('cursor')
- cursor.src = mouseImage
-})
-
-socket.on('highlight', (msg) => {
- // Highlight the anticipated space
- console.log('highlight recieved')
- parsedMsg = JSON.parse(msg)
- highlightMove(parsedMsg.from, parsedMsg.to, 'lastMove')
-})
-
-socket.on('addgrey', (msg) => {
- // Highlight the last moved spaces
- parsedMsg = JSON.parse(msg)
- greySquare(parsedMsg.to)
-})
-
-socket.on('removegrey', () => {
- removeGreySquares()
-})
-
-// handle change of opponents mousexy
-socket.on('mousexy', (msg) => {
- let parsedMsg = JSON.parse(msg)
-
- let viewportWidth = window.innerWidth
- let viewportHeight = window.innerHeight
-
- if (parsedMsg.x && parsedMsg.y) {
- if (isPuzzle) {
- opponentMouseX = parsedMsg.x - 28
- opponentMouseY = parsedMsg.y - 28
- } else {
- opponentMouseX = (-1 * parsedMsg.x) + viewportWidth - 28
- opponentMouseY = (-1 * parsedMsg.y) + viewportHeight - 28
- }
-
- updateOpponentMouseXY()
- }
-})
-
-// Handle reset message from the client
-socket.on('reset', () => {
- // reload page
- location.reload()
- // deleteAllCookies();
- console.log('resetting board')
-})
-
-// Handle lastmove message from the client
-socket.on('lastmove', (msg) => {
- // Highlight the last moved spaces
- parsedMsg = JSON.parse(msg)
- if (!parsedMsg.from && !parsedMsg.to && nextPuzzleMove.length > 0) return
- highlightMove(parsedMsg.from, parsedMsg.to, 'lastmove')
-})
-
-// Deletes all cookies on iframe
-function deleteAllCookies () {
- const cookies = document.cookie.split(';')
-
- for (let cookie of cookies) {
- const cookieName = cookie.split('=')[0].trim()
- deleteCookie(cookieName)
- }
-}
-
-// Listen to message from parent window
-eventer(
- messageEvent,
- (e) => {
- console.log('client event: ', e) // uncomment for debugging
- let data
- try {
- data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data
- } catch (err) {
- console.error('Invalid JSON message from parent:', e.data)
- return
- }
-
- updateStatus()
- board.position(currentState.fen())
- sendToParent(currentState.fen())
-
- // get command from parent and send to server
- var command = data.command
- if (command == 'newgame') { // front end wants to create / join game
- sendNewGame()
- } else if (command == 'newPuzzle') { // front end wants to create / join puzzle
- sendNewPuzzle()
- } else if (command == 'message') { // front end wants to broadcast a message
- sendMessage(data.message)
- } else if (command == 'endgame') { // front end wants to end existing game / puzzle
- // delete game on server
- sendEndGame()
- } else if (command == 'userinfo') {
- mentor = data.mentor
- student = data.student
- role = data.role
- } else if (command == 'undo') { sendUndo() }
-
- // check if puzzle
- if (data.PuzzleId) {
- console.log('loading puzzle: ', data.PuzzleId)
- currentState.load(data.FEN)
- board.position(data.FEN)
-
- // find the starting color
- var activeColor = data.FEN.split(' ')[1]
- // the computer makes the first move
- // change board orientation accordingly
- // i.e. if active color is w, then player is black, and vice versa
- if (activeColor === 'w') {
- playerColor = 'black'
- board.orientation('black')
- } else {
- playerColor = 'white'
- board.orientation('white')
- }
-
- sendSetStateColor(currentState.fen(), playerColor, data.Hints)
- sendToParent(currentState.fen())
- }
-
- // get and set lessonflag
- lessonFlag = data.lessonFlag
- if (lessonFlag == true) {
- isLesson = true
- }
-
- // move a piece if it's a move message
- if ('from' in data && 'to' in data) {
- currentState.move({ from: data.from, to: data.to })
-
- // move highlight
- highlightMove(data.from, data.to)
-
- updateStatus()
- sendToParent(currentState.fen())
- }
-
- // highlight message
- if ('highlightFrom' in data && 'highlightTo' in data) {
- highlightMove(data.highlightFrom, data.highlightTo, 'lastmove')
- }
-
- if ('clearhighlight' in data) {
- $board.find('.' + squareClass).removeClass('lastmove')
- }
-
- // if this is a lesson, setup lesson
- if (isLesson == true) {
- endSquare = data.endSquare
- lessonBoard = data.boardState
- lessonEnd = data.endState
-
- if (
- (data.color === 'black' || data.color === 'white') &&
- data.color !== playerColor
- ) {
- playerColor = data.color
- console.log('data.color: ' + data.color)
- console.log('setting orientation to: ' + playerColor)
- board.orientation(playerColor)
- }
-
- previousEndSquare = data.previousEndSquare
-
- if (previousEndSquare !== '') {
- $board.find('.square-' + previousEndSquare).removeClass('highlight')
- }
-
- if (lessonStarted == false) {
- var lessonConfig = {
- draggable: true,
- showNotation: true,
- position: lessonBoard,
- onDragStart: onDragStart,
- onDrop: onDrop,
- onMouseoutSquare: onMouseoutSquare,
- onMouseoverSquare: onMouseoverSquare,
- onSnapEnd: onSnapEnd
- }
- board = Chessboard('myBoard', lessonConfig)
- // var overlay = new ChessboardArrows('board_wrapper');
- lessonStarted = true
- currentState.load(lessonBoard)
- } else {
- board.position(data.boardState)
- currentState.load(data.boardState)
- updateStatus()
- }
-
- $board.find('.square-' + endSquare).addClass('highlight')
- } else if (data.boardState == defaultFEN) {
- currentState = new Chess()
- }
-
- if (isLesson == false && data.color && data.boardState) {
- playerColor = data.color
- board.orientation(playerColor)
- currentState.load(data.boardState)
- board.position(data.boardState)
- updateStatus()
- }
-
- // highlight message
- // if ("highlightFrom" in data && "highlightTo" in data) {
- // highlightMove(data.highlightFrom, data.highlightTo);
- // }
- },
- false
-)
-
-/**
- * Highlights move squares on the board
- * @param {string} from - Source square
- * @param {string} to - Target square
- * @param {string} style - CSS class name to apply
- */
-function highlightMove (from, to, style) {
- console.log('hightlight!!!', from, to)
- $board.find('.' + squareClass).removeClass(style)
- if (from !== 'remove' || to !== 'remove') {
- if (from && to) {
- highlightFrom = from
- highlightTo = to
- }
- $board.find('.square-' + from).addClass(style)
- $board.find('.square-' + to).addClass(style)
- }
-}
-
-/**
- * Flips the board orientation
- */
-function flip () {
- board.flip()
-}
-
-/**
- * Notifies parent window that iframe is ready to receive messages
- */
-function letParentKnow () {
- if (flag === false) {
- parent.postMessage('ReadyToRecieve', '*')
- }
- flag = true
-}
-
-/**
- * Validates whether a piece can be dragged
- * Checks turn, player color, and game state
- */
-function onDragStart (source, piece, position, orientation) {
- // if freeplay mode is off
- if (!freemoveFlag) {
- if (!playerColor) {
- console.log('Player color not set')
- }
-
- // if it's your turn
- if (playerColor && playerColor[0] == currentState.turn()) {
- // do not pick up pieces if the game is over
- if (isLesson == false) {
- if (currentState.game_over()) {
- sendGameOver()
- return false
- }
- }
-
- if (playerColor === 'black') {
- if (piece.search(/^w/) !== -1) return false
- } else if (playerColor === 'white') {
- if (piece.search(/^b/) !== -1) return false
- }
-
- // only pick up pieces for the side to move
- if (
- (currentState.turn() === 'w' && piece.search(/^b/) !== -1) ||
- (currentState.turn() === 'b' && piece.search(/^w/) !== -1)
- ) {
- return false
- }
-
- sendPieceDrag(piece)
- }
- } else {
- return true
- }
-}
-
-/**
- * Handles piece drop event
- * Validates move legality and updates game state
- */
-function onDrop (source, target, draggedPieceSource) {
- removeGreySquares()
- sendPieceDrop()
-
- // if we're not in freeplay
- if (!freemoveFlag) {
- // if we're doing a puzzle, check if the move is correct
- if (nextPuzzleMove.length == 2) {
- // incorrect move, snapback
- if (source !== nextPuzzleMove[0] || target !== nextPuzzleMove[1]) {
- return 'snapback'
- }
- // correct move, clear the next expected move
- else { nextPuzzleMove = [] }
- }
-
- // see if the move is legal
- var move = currentState.move({
- from: source,
- to: target,
- promotion: 'q' // NOTE: always promote to a queen for example simplicity
- })
-
- // illegal move
- if (move === null) { return 'snapback' }
- // legal move
- else { sendMove(source, target) };
-
- if (isLesson == false) {
- if (currentState.game_over()) {
- sendGameOver()
- }
- }
-
- // move highlight
- highlightMove(source, target, 'lastmove')
- // move highlight of mentor/student
- sendLastMove(source, target)
-
- updateStatus()
- sendToParent(`piece-${draggedPieceSource}`)
- sendToParent(
- JSON.stringify({
- from: source,
- to: target
- })
- )
- sendToParent(`target:${move.to}`)
- sendToParent(currentState.fen())
- }
-}
-
-/**
- * Shows possible moves when hovering over a square
- * Displays grey dots on valid destination squares
- */
-function onMouseoverSquare (square, piece) {
- if (playerColor && playerColor[0] == currentState.turn()) {
- // get list of possible moves for this square
- var moves = currentState.moves({
- square: square,
- verbose: true
- })
-
- // exit if there are no moves available for this square
- if (moves.length === 0) return
-
- // highlight the possible squares for this piece
- for (var i = 0; i < moves.length; i++) {
- greySquare(moves[i].to)
- sendGreySquare(moves[i].to)
- }
- }
-}
-
-/**
- * Removes possible move suggestions when mouse leaves square
- */
-function onMouseoutSquare (square, piece) {
- removeGreySquares()
- sendRemoveGrey()
-}
-
-/**
- * Sends data to parent window via postMessage
- * @param {string} fen - Data to send (typically FEN or event info)
- */
-function sendToParent (fen) {
- parent.postMessage(fen, '*')
-}
-
-/**
- * Updates board position after piece snap animation
- * Handles special moves like castling, en passant, pawn promotion
- */
-function onSnapEnd () {
- board.position(currentState.fen())
-}
-
-/**
- * Updates game status text and checks for game over conditions
- */
-function updateStatus () {
- var status = ''
-
- var moveColor = 'White'
-
- if (currentState.turn() === 'b') {
- moveColor = 'Black'
- }
-
- // checkmate?
- if (isLesson == false) {
- if (currentState.in_checkmate()) {
- status = 'Game over, ' + moveColor + ' is in checkmate.'
- sendCheckmate()
- }
-
- // draw?
- else if (currentState.in_draw()) {
- status = 'Game over, drawn position'
- sendDraw()
- }
- }
-
- // game still on
- else {
- status = moveColor + ' to move'
-
- // check?
- if (currentState.in_check()) {
- status += ', ' + moveColor + ' is in check'
- }
-
- if (currentState.game_over()) {
- if (currentState.in_check() && moveColor == 'Black') {
- parent.postMessage('won:white', '*')
- } else if (currentState.in_check() && moveColor == 'Black') {
- parent.postMessage('won:black', '*')
- } else {
- parent.postMessage('restart', '*')
- }
- }
- }
-}
-
-/**
- * Notifies parent that game is over
- */
-function sendGameOver () {
- parent.postMessage('gameOver', '*')
-}
-
-/**
- * Notifies parent of draw condition
- */
-function sendDraw () {
- parent.postMessage('draw', '*')
-}
-
-/**
- * Notifies parent of checkmate
- */
-function sendCheckmate () {
- parent.postMessage('checkmate', '*')
-}
-
-/**
- * ChessboardArrows - Draws arrows on the chessboard for move annotations
- * @param {string} id - Board container element ID
- * @param {number} RES_FACTOR - Resolution scaling factor
- * @param {string} COLOUR - Arrow color in RGB format
- */
-var ChessboardArrows = function (
- id,
- RES_FACTOR = 2,
- COLOUR = 'rgb(0, 128,0,1)'
-) {
- const NUM_SQUARES = 8
- var resFactor,
- colour,
- drawCanvas,
- drawContext,
- primaryCanvas,
- primaryContext,
- initialPoint,
- mouseDown
-
- resFactor = RES_FACTOR
- colour = COLOUR
-
- // drawing canvas
- drawCanvas = document.getElementById('drawing_canvas')
- drawContext = changeResolution(drawCanvas, resFactor)
- setContextStyle(drawContext)
-
- // primary canvas
- primaryCanvas = document.getElementById('primary_canvas')
- primaryContext = changeResolution(primaryCanvas, resFactor)
- setContextStyle(primaryContext)
-
- // setup mouse event callbacks
- var board = document.getElementById(id)
- board.addEventListener('mousedown', function (event) {
- onMouseDown(event)
- })
- board.addEventListener('mouseup', function (event) {
- onMouseUp(event)
- })
- board.addEventListener('mousemove', function (event) {
- onMouseMove(event)
- })
- board.addEventListener(
- 'contextmenu',
- function (e) {
- e.preventDefault()
- },
- false
- )
-
- // initialise vars
- initialPoint = { x: null, y: null }
- finalPoint = { x: null, y: null }
- arrowWidth = 15
- mouseDown = false
-
- // source: https://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag
- function drawArrow (context, fromx, fromy, tox, toy, r) {
- var x_center = tox
- var y_center = toy
- var angle, x, y
-
- context.beginPath()
-
- angle = Math.atan2(toy - fromy, tox - fromx)
- x = r * Math.cos(angle) + x_center
- y = r * Math.sin(angle) + y_center
-
- context.moveTo(x, y)
-
- angle += (1 / 3) * (2 * Math.PI)
- x = r * Math.cos(angle) + x_center
- y = r * Math.sin(angle) + y_center
-
- context.lineTo(x, y)
-
- angle += (1 / 3) * (2 * Math.PI)
- x = r * Math.cos(angle) + x_center
- y = r * Math.sin(angle) + y_center
-
- context.lineTo(x, y)
- context.closePath()
- context.fill()
- }
-
- function getMousePos (canvas, evt) {
- var rect = canvas.getBoundingClientRect()
- return {
- x: Q(evt.clientX - rect.left),
- y: Q(evt.clientY - rect.top)
- }
- }
-
- function setContextStyle (context) {
- context.strokeStyle = context.fillStyle = colour
- context.lineJoin = 'butt'
- }
-
- function onMouseDown (event) {
- if (event.which == 3) {
- // right click
- mouseDown = true
- initialPoint = finalPoint = getMousePos(drawCanvas, event)
- drawCircle(
- drawContext,
- initialPoint.x,
- initialPoint.y,
- primaryCanvas.width / (resFactor * NUM_SQUARES * 2) - 1
- )
- }
- }
-
- function onMouseUp (event) {
- if (event.which == 3) {
- // right click
- mouseDown = false
- // if starting position == ending position, draw a circle to primary canvas
- if (initialPoint.x == finalPoint.x && initialPoint.y == finalPoint.y) {
- drawCircle(
- primaryContext,
- initialPoint.x,
- initialPoint.y,
- primaryCanvas.width / (resFactor * NUM_SQUARES * 2) - 1
- ) // reduce radius of square by 1px
- }
- // otherwise draw an arrow
- else {
- drawArrowToCanvas(primaryContext)
- }
- drawContext.clearRect(0, 0, drawCanvas.width, drawCanvas.height)
- } else if (event.which == 1) {
- // left click
- // clear canvases
- drawContext.clearRect(0, 0, drawCanvas.width, drawCanvas.height)
- primaryContext.clearRect(0, 0, primaryCanvas.width, primaryCanvas.height)
- }
- }
-
- function onMouseMove (event) {
- finalPoint = getMousePos(drawCanvas, event)
-
- if (!mouseDown) return
- if (initialPoint.x == finalPoint.x && initialPoint.y == finalPoint.y) { return }
-
- drawContext.clearRect(0, 0, drawCanvas.width, drawCanvas.height)
- drawArrowToCanvas(drawContext)
- }
-
- function drawArrowToCanvas (context) {
- // offset finalPoint so the arrow head hits the center of the square
- var xFactor, yFactor, offsetSize
- if (finalPoint.x == initialPoint.x) {
- yFactor = Math.sign(finalPoint.y - initialPoint.y) * arrowWidth
- xFactor = 0
- } else if (finalPoint.y == initialPoint.y) {
- xFactor = Math.sign(finalPoint.x - initialPoint.x) * arrowWidth
- yFactor = 0
- } else {
- // find delta x and delta y to achieve hypotenuse of arrowWidth
- slope_mag = Math.abs(
- (finalPoint.y - initialPoint.y) / (finalPoint.x - initialPoint.x)
- )
- xFactor =
- (Math.sign(finalPoint.x - initialPoint.x) * arrowWidth) /
- Math.sqrt(1 + Math.pow(slope_mag, 2))
- yFactor =
- Math.sign(finalPoint.y - initialPoint.y) *
- Math.abs(xFactor) *
- slope_mag
- }
-
- // draw line
- context.beginPath()
- context.lineCap = 'round'
- context.lineWidth = 8
- context.moveTo(initialPoint.x, initialPoint.y)
- context.lineTo(finalPoint.x - xFactor, finalPoint.y - yFactor)
- context.stroke()
-
- // draw arrow head
- drawArrow(
- context,
- initialPoint.x,
- initialPoint.y,
- finalPoint.x - xFactor,
- finalPoint.y - yFactor,
- arrowWidth
- )
- }
-
- function Q (x, d) {
- // mid-tread quantiser
- d = primaryCanvas.width / (resFactor * NUM_SQUARES)
- return d * (Math.floor(x / d) + 0.5)
- }
-
- function drawCircle (context, x, y, r) {
- context.beginPath()
- context.lineWidth = 4
- context.arc(x, y, r, 0, 2 * Math.PI)
- context.stroke()
- }
-
- // source: https://stackoverflow.com/questions/14488849/higher-dpi-graphics-with-html5-canvas
- function changeResolution (canvas, scaleFactor) {
- // Set up CSS size.
- canvas.style.width = canvas.style.width || canvas.width + 'px'
- canvas.style.height = canvas.style.height || canvas.height + 'px'
-
- // Resize canvas and scale future draws.
- canvas.width = Math.ceil(canvas.width * scaleFactor)
- canvas.height = Math.ceil(canvas.height * scaleFactor)
- var ctx = canvas.getContext('2d')
- ctx.scale(scaleFactor, scaleFactor)
- return ctx
- }
-}
-var config = {
- draggable: true,
- showNotation: true,
- position: 'start',
- orientation: 'white',
- onDragStart: onDragStart,
- onDrop: onDrop,
- onMouseoutSquare: onMouseoutSquare,
- onMouseoverSquare: onMouseoverSquare,
- onSnapEnd: onSnapEnd
-}
-
-if (isLesson == false) {
- // Initialize board with proper sizing
- board = Chessboard('myBoard', config)
-
- // Force a resize to ensure proper dimensions
- setTimeout(function() {
- if (board) {
- board.resize()
- }
- }, 100)
-
- // var overlay = new ChessboardArrows('board_wrapper');
-}
-
-// Handle window resize with proper board resizing
-let resizeTimer
-$(window).resize(function() {
- clearTimeout(resizeTimer)
- resizeTimer = setTimeout(function() {
- if (board) {
- board.resize()
- }
- }, 250)
-})
-
-// Also handle orientation changes on mobile
-$(window).on('orientationchange', function() {
- setTimeout(function() {
- if (board) {
- board.resize()
- }
- }, 500)
-})
-
-updateStatus()
diff --git a/chessClient/js/chessboard-1.0.0.js b/chessClient/js/chessboard-1.0.0.js
deleted file mode 100644
index 89fa6628..00000000
--- a/chessClient/js/chessboard-1.0.0.js
+++ /dev/null
@@ -1,1916 +0,0 @@
-// chessboard.js v1.0.0
-// https://github.com/oakmac/chessboardjs/
-//
-// Copyright (c) 2019, Chris Oakman
-// Released under the MIT license
-// https://github.com/oakmac/chessboardjs/blob/master/LICENSE.md
-
-// start anonymous scope
-(function () {
- 'use strict'
-
- var $ = window['jQuery']
-
- // ---------------------------------------------------------------------------
- // Constants
- // ---------------------------------------------------------------------------
-
- var COLUMNS = 'abcdefgh'.split('')
- var DEFAULT_DRAG_THROTTLE_RATE = 20
- var ELLIPSIS = '…'
- var MINIMUM_JQUERY_VERSION = '1.8.3'
- var RUN_ASSERTS = false
- var START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'
- var START_POSITION = fenToObj(START_FEN)
-
- // default animation speeds
- var DEFAULT_APPEAR_SPEED = 200
- var DEFAULT_MOVE_SPEED = 200
- var DEFAULT_SNAPBACK_SPEED = 60
- var DEFAULT_SNAP_SPEED = 30
- var DEFAULT_TRASH_SPEED = 100
-
- // use unique class names to prevent clashing with anything else on the page
- // and simplify selectors
- // NOTE: these should never change
- var CSS = {}
- CSS['alpha'] = 'alpha-d2270'
- CSS['black'] = 'black-3c85d'
- CSS['board'] = 'board-b72b1'
- CSS['chessboard'] = 'chessboard-63f37'
- CSS['clearfix'] = 'clearfix-7da63'
- CSS['highlight1'] = 'highlight1-32417'
- CSS['highlight2'] = 'highlight2-9c5d2'
- CSS['notation'] = 'notation-322f9'
- CSS['numeric'] = 'numeric-fc462'
- CSS['piece'] = 'piece-417db'
- CSS['row'] = 'row-5277c'
- CSS['sparePieces'] = 'spare-pieces-7492f'
- CSS['sparePiecesBottom'] = 'spare-pieces-bottom-ae20f'
- CSS['sparePiecesTop'] = 'spare-pieces-top-4028b'
- CSS['square'] = 'square-55d63'
- CSS['white'] = 'white-1e1d7'
-
- // ---------------------------------------------------------------------------
- // Misc Util Functions
- // ---------------------------------------------------------------------------
-
- function throttle (f, interval, scope) {
- var timeout = 0
- var shouldFire = false
- var args = []
-
- var handleTimeout = function () {
- timeout = 0
- if (shouldFire) {
- shouldFire = false
- fire()
- }
- }
-
- var fire = function () {
- timeout = window.setTimeout(handleTimeout, interval)
- f.apply(scope, args)
- }
-
- return function (_args) {
- args = arguments
- if (!timeout) {
- fire()
- } else {
- shouldFire = true
- }
- }
- }
-
- // function debounce (f, interval, scope) {
- // var timeout = 0
- // return function (_args) {
- // window.clearTimeout(timeout)
- // var args = arguments
- // timeout = window.setTimeout(function () {
- // f.apply(scope, args)
- // }, interval)
- // }
- // }
-
- function uuid () {
- return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(
- /x/g,
- function (c) {
- var r = (Math.random() * 16) | 0
- return r.toString(16)
- }
- )
- }
-
- function deepCopy (thing) {
- return JSON.parse(JSON.stringify(thing))
- }
-
- function parseSemVer (version) {
- var tmp = version.split('.')
- return {
- major: parseInt(tmp[0], 10),
- minor: parseInt(tmp[1], 10),
- patch: parseInt(tmp[2], 10)
- }
- }
-
- // returns true if version is >= minimum
- function validSemanticVersion (version, minimum) {
- version = parseSemVer(version)
- minimum = parseSemVer(minimum)
-
- var versionNum =
- version.major * 100000 * 100000 + version.minor * 100000 + version.patch
- var minimumNum =
- minimum.major * 100000 * 100000 + minimum.minor * 100000 + minimum.patch
-
- return versionNum >= minimumNum
- }
-
- function interpolateTemplate (str, obj) {
- for (var key in obj) {
- if (!obj.hasOwnProperty(key)) continue
- var keyTemplateStr = '{' + key + '}'
- var value = obj[key]
- while (str.indexOf(keyTemplateStr) !== -1) {
- str = str.replace(keyTemplateStr, value)
- }
- }
- return str
- }
-
- if (RUN_ASSERTS) {
- console.assert(interpolateTemplate('abc', { a: 'x' }) === 'abc')
- console.assert(interpolateTemplate('{a}bc', {}) === '{a}bc')
- console.assert(interpolateTemplate('{a}bc', { p: 'q' }) === '{a}bc')
- console.assert(interpolateTemplate('{a}bc', { a: 'x' }) === 'xbc')
- console.assert(interpolateTemplate('{a}bc{a}bc', { a: 'x' }) === 'xbcxbc')
- console.assert(
- interpolateTemplate('{a}{a}{b}', { a: 'x', b: 'y' }) === 'xxy'
- )
- }
-
- // ---------------------------------------------------------------------------
- // Predicates
- // ---------------------------------------------------------------------------
-
- function isString (s) {
- return typeof s === 'string'
- }
-
- function isFunction (f) {
- return typeof f === 'function'
- }
-
- function isInteger (n) {
- return typeof n === 'number' && isFinite(n) && Math.floor(n) === n
- }
-
- function validAnimationSpeed (speed) {
- if (speed === 'fast' || speed === 'slow') return true
- if (!isInteger(speed)) return false
- return speed >= 0
- }
-
- function validThrottleRate (rate) {
- return isInteger(rate) && rate >= 1
- }
-
- function validMove (move) {
- // move should be a string
- if (!isString(move)) return false
-
- // move should be in the form of "e2-e4", "f6-d5"
- var squares = move.split('-')
- if (squares.length !== 2) return false
-
- return validSquare(squares[0]) && validSquare(squares[1])
- }
-
- function validSquare (square) {
- return isString(square) && square.search(/^[a-h][1-8]$/) !== -1
- }
-
- if (RUN_ASSERTS) {
- console.assert(validSquare('a1'))
- console.assert(validSquare('e2'))
- console.assert(!validSquare('D2'))
- console.assert(!validSquare('g9'))
- console.assert(!validSquare('a'))
- console.assert(!validSquare(true))
- console.assert(!validSquare(null))
- console.assert(!validSquare({}))
- }
-
- function validPieceCode (code) {
- return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1
- }
-
- if (RUN_ASSERTS) {
- console.assert(validPieceCode('bP'))
- console.assert(validPieceCode('bK'))
- console.assert(validPieceCode('wK'))
- console.assert(validPieceCode('wR'))
- console.assert(!validPieceCode('WR'))
- console.assert(!validPieceCode('Wr'))
- console.assert(!validPieceCode('a'))
- console.assert(!validPieceCode(true))
- console.assert(!validPieceCode(null))
- console.assert(!validPieceCode({}))
- }
-
- function validFen (fen) {
- if (!isString(fen)) return false
-
- // cut off any move, castling, etc info from the end
- // we're only interested in position information
- fen = fen.replace(/ .+$/, '')
-
- // expand the empty square numbers to just 1s
- fen = expandFenEmptySquares(fen)
-
- // FEN should be 8 sections separated by slashes
- var chunks = fen.split('/')
- if (chunks.length !== 8) return false
-
- // check each section
- for (var i = 0; i < 8; i++) {
- if (
- chunks[i].length !== 8 ||
- chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1
- ) {
- return false
- }
- }
-
- return true
- }
-
- if (RUN_ASSERTS) {
- console.assert(validFen(START_FEN))
- console.assert(validFen('8/8/8/8/8/8/8/8'))
- console.assert(
- validFen('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')
- )
- console.assert(
- validFen('3r3r/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')
- )
- console.assert(
- !validFen('3r3z/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')
- )
- console.assert(!validFen('anbqkbnr/8/8/8/8/8/PPPPPPPP/8'))
- console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/'))
- console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN'))
- console.assert(!validFen('888888/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'))
- console.assert(!validFen('888888/pppppppp/74/8/8/8/PPPPPPPP/RNBQKBNR'))
- console.assert(!validFen({}))
- }
-
- function validPositionObject (pos) {
- if (!$.isPlainObject(pos)) return false
-
- for (var i in pos) {
- if (!pos.hasOwnProperty(i)) continue
-
- if (!validSquare(i) || !validPieceCode(pos[i])) {
- return false
- }
- }
-
- return true
- }
-
- if (RUN_ASSERTS) {
- console.assert(validPositionObject(START_POSITION))
- console.assert(validPositionObject({}))
- console.assert(validPositionObject({ e2: 'wP' }))
- console.assert(validPositionObject({ e2: 'wP', d2: 'wP' }))
- console.assert(!validPositionObject({ e2: 'BP' }))
- console.assert(!validPositionObject({ y2: 'wP' }))
- console.assert(!validPositionObject(null))
- console.assert(!validPositionObject('start'))
- console.assert(!validPositionObject(START_FEN))
- }
-
- function isTouchDevice () {
- return 'ontouchstart' in document.documentElement
- }
-
- function validJQueryVersion () {
- return (
- typeof window.$ &&
- $.fn &&
- $.fn.jquery &&
- validSemanticVersion($.fn.jquery, MINIMUM_JQUERY_VERSION)
- )
- }
-
- // ---------------------------------------------------------------------------
- // Chess Util Functions
- // ---------------------------------------------------------------------------
-
- // convert FEN piece code to bP, wK, etc
- function fenToPieceCode (piece) {
- // black piece
- if (piece.toLowerCase() === piece) {
- return 'b' + piece.toUpperCase()
- }
-
- // white piece
- return 'w' + piece.toUpperCase()
- }
-
- // convert bP, wK, etc code to FEN structure
- function pieceCodeToFen (piece) {
- var pieceCodeLetters = piece.split('')
-
- // white piece
- if (pieceCodeLetters[0] === 'w') {
- return pieceCodeLetters[1].toUpperCase()
- }
-
- // black piece
- return pieceCodeLetters[1].toLowerCase()
- }
-
- // convert FEN string to position object
- // returns false if the FEN string is invalid
- function fenToObj (fen) {
- if (!validFen(fen)) return false
-
- // cut off any move, castling, etc info from the end
- // we're only interested in position information
- fen = fen.replace(/ .+$/, '')
-
- var rows = fen.split('/')
- var position = {}
-
- var currentRow = 8
- for (var i = 0; i < 8; i++) {
- var row = rows[i].split('')
- var colIdx = 0
-
- // loop through each character in the FEN section
- for (var j = 0; j < row.length; j++) {
- // number / empty squares
- if (row[j].search(/[1-8]/) !== -1) {
- var numEmptySquares = parseInt(row[j], 10)
- colIdx = colIdx + numEmptySquares
- } else {
- // piece
- var square = COLUMNS[colIdx] + currentRow
- position[square] = fenToPieceCode(row[j])
- colIdx = colIdx + 1
- }
- }
-
- currentRow = currentRow - 1
- }
-
- return position
- }
-
- // position object to FEN string
- // returns false if the obj is not a valid position object
- function objToFen (obj) {
- if (!validPositionObject(obj)) return false
-
- var fen = ''
-
- var currentRow = 8
- for (var i = 0; i < 8; i++) {
- for (var j = 0; j < 8; j++) {
- var square = COLUMNS[j] + currentRow
-
- // piece exists
- if (obj.hasOwnProperty(square)) {
- fen = fen + pieceCodeToFen(obj[square])
- } else {
- // empty space
- fen = fen + '1'
- }
- }
-
- if (i !== 7) {
- fen = fen + '/'
- }
-
- currentRow = currentRow - 1
- }
-
- // squeeze the empty numbers together
- fen = squeezeFenEmptySquares(fen)
-
- return fen
- }
-
- if (RUN_ASSERTS) {
- console.assert(objToFen(START_POSITION) === START_FEN)
- console.assert(objToFen({}) === '8/8/8/8/8/8/8/8')
- console.assert(objToFen({ a2: 'wP', b2: 'bP' }) === '8/8/8/8/8/8/Pp6/8')
- }
-
- function squeezeFenEmptySquares (fen) {
- return fen
- .replace(/11111111/g, '8')
- .replace(/1111111/g, '7')
- .replace(/111111/g, '6')
- .replace(/11111/g, '5')
- .replace(/1111/g, '4')
- .replace(/111/g, '3')
- .replace(/11/g, '2')
- }
-
- function expandFenEmptySquares (fen) {
- return fen
- .replace(/8/g, '11111111')
- .replace(/7/g, '1111111')
- .replace(/6/g, '111111')
- .replace(/5/g, '11111')
- .replace(/4/g, '1111')
- .replace(/3/g, '111')
- .replace(/2/g, '11')
- }
-
- // returns the distance between two squares
- function squareDistance (squareA, squareB) {
- var squareAArray = squareA.split('')
- var squareAx = COLUMNS.indexOf(squareAArray[0]) + 1
- var squareAy = parseInt(squareAArray[1], 10)
-
- var squareBArray = squareB.split('')
- var squareBx = COLUMNS.indexOf(squareBArray[0]) + 1
- var squareBy = parseInt(squareBArray[1], 10)
-
- var xDelta = Math.abs(squareAx - squareBx)
- var yDelta = Math.abs(squareAy - squareBy)
-
- if (xDelta >= yDelta) return xDelta
- return yDelta
- }
-
- // returns the square of the closest instance of piece
- // returns false if no instance of piece is found in position
- function findClosestPiece (position, piece, square) {
- // create array of closest squares from square
- var closestSquares = createRadius(square)
-
- // search through the position in order of distance for the piece
- for (var i = 0; i < closestSquares.length; i++) {
- var s = closestSquares[i]
-
- if (position.hasOwnProperty(s) && position[s] === piece) {
- return s
- }
- }
-
- return false
- }
-
- // returns an array of closest squares from square
- function createRadius (square) {
- var squares = []
-
- // calculate distance of all squares
- for (var i = 0; i < 8; i++) {
- for (var j = 0; j < 8; j++) {
- var s = COLUMNS[i] + (j + 1)
-
- // skip the square we're starting from
- if (square === s) continue
-
- squares.push({
- square: s,
- distance: squareDistance(square, s)
- })
- }
- }
-
- // sort by distance
- squares.sort(function (a, b) {
- return a.distance - b.distance
- })
-
- // just return the square code
- var surroundingSquares = []
- for (i = 0; i < squares.length; i++) {
- surroundingSquares.push(squares[i].square)
- }
-
- return surroundingSquares
- }
-
- // given a position and a set of moves, return a new position
- // with the moves executed
- function calculatePositionFromMoves (position, moves) {
- var newPosition = deepCopy(position)
-
- for (var i in moves) {
- if (!moves.hasOwnProperty(i)) continue
-
- // skip the move if the position doesn't have a piece on the source square
- if (!newPosition.hasOwnProperty(i)) continue
-
- var piece = newPosition[i]
- delete newPosition[i]
- newPosition[moves[i]] = piece
- }
-
- return newPosition
- }
-
- // TODO: add some asserts here for calculatePositionFromMoves
-
- // ---------------------------------------------------------------------------
- // HTML
- // ---------------------------------------------------------------------------
-
- function buildContainerHTML (hasSparePieces) {
- var html = '{info}
+{info}
- {/* deactivate previous button, if there are no lessons before it*/} + {/* Navigation buttons */}Back
-Back
-Back
+Back
+Next
Next
Failed to load content
@@ -439,14 +544,14 @@ const LessonOverlay: React.FCLesson completed
@@ -456,35 +561,15 @@ const LessonOverlay: React.FCLesson failed
@@ -494,21 +579,13 @@ const LessonOverlay: React.FCLoading lesson...
@@ -517,39 +594,39 @@ const LessonOverlay: React.FCLesson Instructions
{info}
- {/* Loading bar at the bottom */}🎉 Congratulations!
-- You have completed all lessons for this scenario. -
-You have completed all lessons for this scenario.
+- The computer will think - - moves ahead of you -
-+ The computer will think + setMovesAhead(parseInt(e.target.value) || 5)} + disabled={!socket.connected} + /> + moves ahead of you +
+Disconnected from server
+ )} +