├── frontend ├── .babelrc ├── .eslintrc.json ├── public │ ├── b_b.png │ ├── b_w.png │ ├── k_b.png │ ├── k_w.png │ ├── logo.png │ ├── n_b.png │ ├── n_w.png │ ├── p_b.png │ ├── p_w.png │ ├── q_b.png │ ├── q_w.png │ ├── r_b.png │ ├── r_w.png │ ├── chess.jpg │ ├── chess.png │ ├── favicon.ico │ ├── solana.png │ ├── Icon │ │ ├── amazon.png │ │ ├── discord.png │ │ ├── netflix.png │ │ ├── reddit.png │ │ ├── spotify.png │ │ ├── eva_arrow-next-fill.svg │ │ ├── eva_arrow-back-fill.svg │ │ ├── facebook.svg │ │ ├── checklist.svg │ │ ├── twitter.svg │ │ ├── stars.svg │ │ ├── heroicons_sm-user.svg │ │ ├── jam_check.svg │ │ ├── instagram.svg │ │ ├── gridicons_location.svg │ │ └── bx_bxs-server.svg │ ├── chess-icon.png │ ├── Illustration1.png │ ├── Illustration2.png │ ├── chess-concept.jpg │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── github-brands.svg │ └── safari-pinned-tab.svg ├── postcss.config.js ├── src │ ├── assets │ │ ├── Icon │ │ │ ├── amazon.png │ │ │ ├── discord.png │ │ │ ├── netflix.png │ │ │ ├── reddit.png │ │ │ ├── spotify.png │ │ │ ├── eva_arrow-back-fill.svg │ │ │ ├── eva_arrow-next-fill.svg │ │ │ ├── facebook.svg │ │ │ ├── checklist.svg │ │ │ ├── twitter.svg │ │ │ ├── stars.svg │ │ │ ├── heroicons_sm-user.svg │ │ │ ├── jam_check.svg │ │ │ ├── instagram.svg │ │ │ ├── gridicons_location.svg │ │ │ └── bx_bxs-server.svg │ │ ├── Illustration1.png │ │ └── Illustration2.png │ ├── schema │ │ ├── gameInformation.ts │ │ ├── Member.ts │ │ ├── ChessPiece.ts │ │ └── enums.ts │ ├── data │ │ ├── parameters.ts │ │ ├── mockNFT.ts │ │ └── chessPieces.ts │ ├── components │ │ ├── Square.jsx │ │ ├── LayoutLanding │ │ │ ├── LayoutLanding.jsx │ │ │ ├── FooterLanding.jsx │ │ │ └── HeaderLanding.jsx │ │ ├── misc │ │ │ ├── ButtonOutline..js │ │ │ └── ButtonPrimary.tsx │ │ ├── Layout.tsx │ │ ├── Promote.jsx │ │ ├── dialogs │ │ │ ├── Loading.tsx │ │ │ ├── PopUp.tsx │ │ │ └── ModalDialog.tsx │ │ ├── ModalIntruder.tsx │ │ ├── Piece.tsx │ │ ├── MyChessPieces.tsx │ │ ├── Seo.tsx │ │ ├── BoardSquare.tsx │ │ ├── Board.tsx │ │ ├── SharingButton.tsx │ │ ├── Wallet.tsx │ │ ├── DescriptionGame.tsx │ │ ├── HeaderOriginal.tsx │ │ ├── MainBlockChessNewLayout.tsx │ │ ├── GameListTable.tsx │ │ └── Game.ts │ ├── styles │ │ └── globals.css │ ├── firebase.js │ ├── pages │ │ ├── api │ │ │ ├── finish-game.ts │ │ │ ├── init-wager.ts │ │ │ ├── accept-wager.ts │ │ │ └── game-status.ts │ │ ├── index.tsx │ │ ├── _app.tsx │ │ ├── signin.tsx │ │ └── game │ │ │ └── [game].tsx │ ├── contexts │ │ ├── LoadingContext.tsx │ │ ├── RouteGuarding.tsx │ │ └── ModalContext.tsx │ └── styles.css ├── next-env.d.ts ├── .env.example ├── config.json ├── tsconfig.json ├── next.config.js ├── .gitignore ├── tailwind.config.js └── package.json ├── .gitignore ├── .prettierignore ├── programs └── chess │ ├── src │ ├── lib.rs │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── state.rs │ └── processor.rs │ └── Cargo.toml ├── Cargo.toml ├── tsconfig.json ├── migrations └── deploy.ts ├── Anchor.toml ├── package.json └── README.md /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["next/babel"], "plugins": [] } -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .yarn 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /frontend/public/b_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/b_b.png -------------------------------------------------------------------------------- /frontend/public/b_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/b_w.png -------------------------------------------------------------------------------- /frontend/public/k_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/k_b.png -------------------------------------------------------------------------------- /frontend/public/k_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/k_w.png -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/n_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/n_b.png -------------------------------------------------------------------------------- /frontend/public/n_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/n_w.png -------------------------------------------------------------------------------- /frontend/public/p_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/p_b.png -------------------------------------------------------------------------------- /frontend/public/p_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/p_w.png -------------------------------------------------------------------------------- /frontend/public/q_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/q_b.png -------------------------------------------------------------------------------- /frontend/public/q_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/q_w.png -------------------------------------------------------------------------------- /frontend/public/r_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/r_b.png -------------------------------------------------------------------------------- /frontend/public/r_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/r_w.png -------------------------------------------------------------------------------- /frontend/public/chess.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/chess.jpg -------------------------------------------------------------------------------- /frontend/public/chess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/chess.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/solana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/solana.png -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/Icon/amazon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Icon/amazon.png -------------------------------------------------------------------------------- /frontend/public/Icon/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Icon/discord.png -------------------------------------------------------------------------------- /frontend/public/Icon/netflix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Icon/netflix.png -------------------------------------------------------------------------------- /frontend/public/Icon/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Icon/reddit.png -------------------------------------------------------------------------------- /frontend/public/Icon/spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Icon/spotify.png -------------------------------------------------------------------------------- /frontend/public/chess-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/chess-icon.png -------------------------------------------------------------------------------- /frontend/public/Illustration1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Illustration1.png -------------------------------------------------------------------------------- /frontend/public/Illustration2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/Illustration2.png -------------------------------------------------------------------------------- /frontend/public/chess-concept.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/chess-concept.jpg -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/src/assets/Icon/amazon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Icon/amazon.png -------------------------------------------------------------------------------- /frontend/src/assets/Icon/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Icon/discord.png -------------------------------------------------------------------------------- /frontend/src/assets/Icon/netflix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Icon/netflix.png -------------------------------------------------------------------------------- /frontend/src/assets/Icon/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Icon/reddit.png -------------------------------------------------------------------------------- /frontend/src/assets/Icon/spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Icon/spotify.png -------------------------------------------------------------------------------- /frontend/src/assets/Illustration1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Illustration1.png -------------------------------------------------------------------------------- /frontend/src/assets/Illustration2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/src/assets/Illustration2.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutupdev/Solana-Chess-Casino-Game/HEAD/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/src/schema/gameInformation.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface GameInformation { 4 | gameId: string; 5 | gameCreatedAt: number; 6 | status: string; 7 | } -------------------------------------------------------------------------------- /programs/chess/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod instruction; 3 | pub mod processor; 4 | pub mod state; 5 | 6 | #[cfg(not(feature = "no-entrypoint"))] 7 | pub mod entrypoint; -------------------------------------------------------------------------------- /frontend/src/schema/Member.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | uid?: string; 3 | name: string; 4 | piece: string; 5 | creator: boolean; 6 | hasBetChessPiece: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/schema/ChessPiece.ts: -------------------------------------------------------------------------------- 1 | 2 | // Interface for the chess pieces 3 | export interface ChessPiece { 4 | name: string; 5 | id: string; 6 | imageURL: string; 7 | isMissing: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/schema/enums.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export enum gameStatusEnum { 4 | Waiting = 'waiting', 5 | TwoPlayersPresent = 'twoPlayersPresent', 6 | Ready = 'ready', 7 | Finished = 'finished' 8 | } 9 | -------------------------------------------------------------------------------- /frontend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /frontend/public/Icon/eva_arrow-next-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/Icon/eva_arrow-back-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/eva_arrow-back-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/eva_arrow-next-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | [profile.release] 7 | overflow-checks = true 8 | lto = "fat" 9 | codegen-units = 1 10 | [profile.release.build-override] 11 | opt-level = 3 12 | incremental = false 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /frontend/src/data/parameters.ts: -------------------------------------------------------------------------------- 1 | export const isMockNFT = false; 2 | 3 | export const defaultName = "What's my name ?"; 4 | 5 | export const SECONDS_BEFORE_CANCELLING = 60; 6 | 7 | export const NUMBER_CHESS_PIECES_NEEDED = 2; 8 | 9 | export const NUMBER_SECONDS_GAME = 180; -------------------------------------------------------------------------------- /frontend/public/Icon/facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Square.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Square({ children, black }) { 4 | const bgClass = black ? 'square-white' : 'square-black' 5 | 6 | return ( 7 |
8 | {children} 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /programs/chess/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wager-sol-escrow" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | no-entrypoint = [] 8 | 9 | [dependencies] 10 | solana-program = "1.9.4" 11 | thiserror = "1.0.24" 12 | arrayref = "0.3.6" 13 | 14 | [lib] 15 | crate-type = ["cdylib", "lib"] 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": [ 4 | "mocha", 5 | "chai" 6 | ], 7 | "typeRoots": [ 8 | "./node_modules/@types" 9 | ], 10 | "lib": [ 11 | "es2015" 12 | ], 13 | "module": "commonjs", 14 | "target": "es6", 15 | "esModuleInterop": true, 16 | "resolveJsonModule": true 17 | } 18 | } -------------------------------------------------------------------------------- /frontend/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Chrome, Safari, Edge, Opera */ 6 | input::-webkit-outer-spin-button, 7 | input::-webkit-inner-spin-button { 8 | -webkit-appearance: none; 9 | margin: 0; 10 | } 11 | 12 | /* Firefox */ 13 | input[type="number"] { 14 | -moz-appearance: textfield; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/components/LayoutLanding/LayoutLanding.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import HeaderLanding from "./HeaderLanding"; 3 | import FooterLanding from "./FooterLanding"; 4 | 5 | const LayoutLanding = ({ children }) => { 6 | return ( 7 | <> 8 | 9 | {children} 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default LayoutLanding; 16 | -------------------------------------------------------------------------------- /migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | seeds = true 5 | skip-lint = false 6 | 7 | [programs.devnet] 8 | chess = "7wUQXRQtBzTmyp9kcrmok9FKcc4RSYXxPYN9FGDLnqxb" 9 | 10 | [registry] 11 | url = "https://devnet.helius-rpc.com/?api-key=" 12 | 13 | [provider] 14 | cluster = "Devnet" 15 | wallet = "./id.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | 20 | [test] 21 | startup_wait = 10000 22 | shutdown_wait = 2000 23 | upgradeable = false 24 | -------------------------------------------------------------------------------- /frontend/src/components/misc/ButtonOutline..js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ButtonOutline = ({ children }) => { 4 | return ( 5 | 9 | ); 10 | }; 11 | 12 | export default ButtonOutline; 13 | -------------------------------------------------------------------------------- /programs/chess/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, 3 | entrypoint, 4 | msg, 5 | entrypoint::ProgramResult, 6 | pubkey::Pubkey, 7 | }; 8 | 9 | use crate::processor::Processor; 10 | 11 | entrypoint!(process_instruction); 12 | fn process_instruction( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | instruction_data: &[u8], 16 | ) -> ProgramResult { 17 | msg!("EntryPoint OK"); 18 | Processor::process( accounts, instruction_data, program_id) 19 | } -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_BUILD_ENV=prod 2 | NEXT_PUBLIC_FIREBASE_API_KEY=YOUR_FIREBASE_API_KEY 3 | NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=YOUR_FIREBASE_AUTH_DOMAIN 4 | NEXT_PUBLIC_FIREBASE_PROJECT_ID=YOUR_FIREBASE_PROJECT_ID 5 | NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=YOUR_FIREBASE_STORAGE_BUCKET 6 | NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=YOUR_FIREBASE_MESSAGING_SENDER_ID 7 | NEXT_PUBLIC_FIREBASE_APP_ID=YOUR_FIREBASE_APP_ID 8 | BACKEND_ENDPOINT=https://web2to3.herokuapp.com 9 | NEXT_PUBLIC_WEBSITE_HOST=YOUR_DOMAIN_NAME 10 | API_KEY=YOUR_API_KEY -------------------------------------------------------------------------------- /frontend/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": 0.01, 3 | "number": 320, 4 | "gatekeeper": null, 5 | "solTreasuryAccount": "5nbAAqmheK8ABAqPys3CkmuvSTBSiTn143AQrUEJBhru", 6 | "splTokenAccount": null, 7 | "splToken": null, 8 | "goLiveDate": "25 Dec 2021 00:00:00 GMT", 9 | "endSettings": null, 10 | "whitelistMintSettings": null, 11 | "hiddenSettings": null, 12 | "storage": "arweave-sol", 13 | "ipfsInfuraProjectId": null, 14 | "ipfsInfuraSecret": null, 15 | "awsS3Bucket": null, 16 | "noRetainAuthority": false, 17 | "noMutable": false 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/data/mockNFT.ts: -------------------------------------------------------------------------------- 1 | export const nftFilteredFull = [ 2 | {data: {name: 'Pawn'}}, 3 | {data: {name: 'Pawn'}}, 4 | {data: {name: 'Pawn'}}, 5 | {data: {name: 'Pawn'}}, 6 | {data: {name: 'Pawn'}}, 7 | {data: {name: 'Pawn'}}, 8 | {data: {name: 'Pawn'}}, 9 | {data: {name: 'Pawn'}}, 10 | {data: {name: 'King'}}, 11 | {data: {name: 'King'}}, 12 | {data: {name: 'Queen'}}, 13 | {data: {name: 'Knight'}}, 14 | {data: {name: 'Knight'}}, 15 | {data: {name: 'Bishop'}}, 16 | {data: {name: 'Bishop'}}, 17 | {data: {name: 'Rook'}}, 18 | {data: {name: 'Rook'}} 19 | ]; 20 | -------------------------------------------------------------------------------- /frontend/public/Icon/checklist.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/checklist.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/Icon/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "^0.29.0", 8 | "@solana/spl-token": "^0.4.6", 9 | "@solana/web3.js": "^1.91.8" 10 | }, 11 | "devDependencies": { 12 | "@types/bn.js": "^5.1.0", 13 | "@types/chai": "^4.3.0", 14 | "@types/mocha": "^9.0.0", 15 | "chai": "^4.3.4", 16 | "mocha": "^9.0.3", 17 | "prettier": "^2.6.2", 18 | "ts-mocha": "^10.0.0", 19 | "typescript": "^4.3.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Toaster } from "react-hot-toast"; 3 | import { HeaderOriginal } from "./HeaderOriginal"; 4 | 5 | export const Layout = ({ 6 | children, 7 | }: { 8 | children: React.ReactNode; 9 | }) => { 10 | 11 | 12 | return ( 13 |
14 | 15 |
{children}
16 |
17 |
18 | 2022 Chess2Earn - All rights reserved 19 |
20 |
21 | 22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/components/Promote.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Square from './Square' 3 | import { move } from './Game' 4 | import Image from "next/image"; 5 | const promotionPieces = ['r', 'n', 'b', 'q'] 6 | 7 | export default function Promote({ 8 | promotion: { from, to, color }, 9 | }) { 10 | return ( 11 |
12 | {promotionPieces.map((p, i) => ( 13 |
14 | 15 |
move(from, to, p)} 18 | > 19 | chessPiece 21 |
22 |
23 |
24 | ))} 25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app'; 2 | import 'firebase/firestore'; 3 | import 'firebase/auth'; 4 | import 'firebase/database'; 5 | 6 | 7 | const firebaseConfig = { 8 | "apiKey": process.env.NEXT_PUBLIC_FIREBASE_API_KEY, 9 | "authDomain":process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, 10 | "projectId": process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 11 | "storageBucket": process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, 12 | "messagingSenderId": process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, 13 | "appId": process.env.NEXT_PUBLIC_FIREBASE_APP_ID 14 | } 15 | 16 | 17 | // Initialize Firebase 18 | if (!firebase.apps.length) { 19 | firebase.initializeApp(firebaseConfig); 20 | } 21 | export const db = firebase.firestore(); 22 | export const realTimeDB = firebase.database(); 23 | export const auth = firebase.auth(); 24 | export default firebase; 25 | -------------------------------------------------------------------------------- /frontend/public/Icon/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/misc/ButtonPrimary.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface IProps { 4 | children: any; 5 | isColorBlack: boolean; 6 | } 7 | 8 | const ButtonPrimary = ({ children, isColorBlack }: IProps) => { 9 | 10 | if (isColorBlack) { 11 | return ( 12 | 19 | ); 20 | } else { 21 | return ( 22 | 29 | ); 30 | } 31 | }; 32 | 33 | export default ButtonPrimary; 34 | -------------------------------------------------------------------------------- /frontend/src/pages/api/finish-game.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next' 2 | 3 | type Data = { 4 | name: string 5 | } 6 | 7 | export default function finishGameHandler( 8 | req: NextApiRequest, 9 | res: NextApiResponse 10 | ) { 11 | const bodyRequest = req.body; 12 | bodyRequest.api_key = process.env.API_KEY; 13 | fetch(process.env.BACKEND_ENDPOINT + '/finish-game', { 14 | method: 'post', 15 | headers: { 'Content-Type': 'application/json' }, 16 | body: JSON.stringify(bodyRequest) 17 | }) 18 | .then(response => response.json()) 19 | .then(data => { 20 | console.log('[SERVER] Data received', data) 21 | res.status(200).json(data); 22 | }) 23 | .catch(error => 24 | { 25 | console.log('[SERVER] Error', error); 26 | res.status(201).json(error.message); 27 | } 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/components/dialogs/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog } from "@headlessui/react"; 2 | import { useLoadingState } from "../../contexts/LoadingContext"; 3 | 4 | export const Loading = () => { 5 | const state = useLoadingState(); 6 | 7 | return ( 8 | {}} 11 | className="fixed z-10 inset-0 overflow-y-auto" 12 | > 13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | const withTM = require("next-transpile-modules")([ 2 | "play2earn", 3 | 4 | // "@blocto/sdk", 5 | "@project-serum/sol-wallet-adapter", 6 | "@solana/wallet-adapter-base", 7 | "@solana/wallet-adapter-react", 8 | "@solana/wallet-adapter-wallets", 9 | "@solana/wallet-adapter-react-ui", 10 | "@solana/wallet-adapter-bitpie", 11 | // "@solana/wallet-adapter-blocto", 12 | "@solana/wallet-adapter-coin98", 13 | "@solana/wallet-adapter-ledger", 14 | "@solana/wallet-adapter-mathwallet", 15 | "@solana/wallet-adapter-phantom", 16 | "@solana/wallet-adapter-slope", 17 | "@solana/wallet-adapter-solflare", 18 | "@solana/wallet-adapter-sollet", 19 | "@solana/wallet-adapter-solong", 20 | "@solana/wallet-adapter-torus" 21 | ]); 22 | 23 | /** @type {import('next').NextConfig} */ 24 | module.exports = withTM({ 25 | reactStrictMode: true, 26 | images: { 27 | domains: ["www.arweave.net", "gateway.pinata.cloud"], 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /frontend/public/Icon/heroicons_sm-user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/Icon/jam_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/heroicons_sm-user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/Icon/instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/jam_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/Icon/gridicons_location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/gridicons_location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/ModalIntruder.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | 4 | interface IProps { 5 | textMessage: string; 6 | } 7 | 8 | /** 9 | * Modal intruder based on the status of the game. 10 | * @param textMessage: string 11 | * @constructor 12 | */ 13 | export const ModalIntruder = ({textMessage}: IProps) => { 14 | 15 | return ( 16 |
17 |
18 |

{textMessage}

19 | 20 | 21 | 24 | 25 | 26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/pages/api/init-wager.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next' 2 | 3 | type Data = { 4 | name: string 5 | } 6 | 7 | export default function initWagerHandler( 8 | req: NextApiRequest, 9 | res: NextApiResponse 10 | ) { 11 | const bodyRequest = req.body; 12 | bodyRequest.api_key = process.env.API_KEY; 13 | console.log('[SERVER] Request body with API', bodyRequest); 14 | fetch(process.env.BACKEND_ENDPOINT + '/init-wager', { 15 | method: 'post', 16 | headers: { 'Content-Type': 'application/json' }, 17 | body: JSON.stringify(bodyRequest) 18 | }) 19 | .then(response => response.json()) 20 | .then(data => { 21 | console.log('[SERVER] Data received: ', data) 22 | res.status(200).json(data); 23 | }) 24 | .catch(error => 25 | { 26 | console.log('[SERVER] Error', error); 27 | res.status(201).json(error.message); 28 | } 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /programs/chess/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use solana_program::program_error::ProgramError; 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum EscrowError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | /// Not Rent Exempt 11 | #[error("Not Rent Exempt")] 12 | NotRentExempt, 13 | /// Expected Amount Mismatch 14 | #[error("Expected Amount Mismatch")] 15 | ExpectedAmountMismatch, 16 | /// Amount Overflow 17 | #[error("Amount Overflow")] 18 | AmountOverflow, 19 | /// Invalid Account 20 | #[error("Invalid Account")] 21 | InvalidAccount, 22 | /// Invalid Amount 23 | #[error("Invalid Amount")] 24 | InvalidAmount, 25 | /// Invalid Owner 26 | #[error("Invalid Owner")] 27 | InvalidOwner, 28 | #[error("Invalid PDA Seeds")] 29 | InvalidPdaSeeds, 30 | 31 | } 32 | 33 | impl From for ProgramError { 34 | fn from(e: EscrowError) -> Self { 35 | ProgramError::Custom(e as u32) 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/src/pages/api/accept-wager.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next' 2 | 3 | type Data = { 4 | name: string 5 | } 6 | 7 | export default function acceptWagerHandler( 8 | req: NextApiRequest, 9 | res: NextApiResponse 10 | ) { 11 | console.log('[SERVER] Request body', req.body); 12 | const bodyRequest = req.body; 13 | bodyRequest.api_key = process.env.API_KEY; 14 | console.log('[SERVER] Request body with API', bodyRequest); 15 | fetch(process.env.BACKEND_ENDPOINT + '/accept-wager', { 16 | method: 'post', 17 | headers: { 'Content-Type': 'application/json' }, 18 | body: JSON.stringify(bodyRequest) 19 | }) 20 | .then(response => response.json()) 21 | .then(data => { 22 | console.log('[SERVER] Data received', data) 23 | res.status(200).json(data); 24 | }) 25 | .catch(error => 26 | { 27 | console.log('[SERVER] Error', error); 28 | res.status(201).json(error.message); 29 | } 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/pages/api/game-status.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next' 2 | 3 | type Data = { 4 | name: string 5 | } 6 | 7 | // Fetch the game status with the API 8 | export default function gameStatusHandler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | console.log('[SERVER] Request body', req.body); 13 | const bodyRequest = req.body; 14 | bodyRequest.api_key = process.env.API_KEY; 15 | console.log('[SERVER] Request body with API', bodyRequest); 16 | fetch(process.env.BACKEND_ENDPOINT + '/game-status', { 17 | method: 'post', 18 | headers: { 'Content-Type': 'application/json' }, 19 | body: JSON.stringify(bodyRequest) 20 | }) 21 | .then(response => response.json()) 22 | .then(data => { 23 | console.log('[SERVER] Data received', data) 24 | res.status(200).json(data); 25 | }) 26 | .catch(error => 27 | { 28 | console.log('[SERVER] Error', error); 29 | res.status(201).json(error.message); 30 | } 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana Chess Casino Game 2 | 3 | This is chess game on the Solana blockchain. As a play-to-earn game, I have implemented smart contract for security. Solana program was built by Anchor framework and UI is built by react.js. This is not full code, I have shared only UI and smart contract here. For full working website, feel free to reach out of me when you need supports[Telegram: https://t.me/DevCutup, Whatspp: https://wa.me/13137423660]. 4 | 5 | 6 | 7 | ## How to use it 8 | 9 | ```bash 10 | git clone https://github.com/cutupdev/Solana-Chess-Casino-Game.git 11 | ``` 12 | 13 | ```bash 14 | cd ./Solana-Chess-Casino-Game 15 | ``` 16 | 17 | ```bash 18 | npm run install 19 | ``` 20 | 21 | - For smart contract deployment: 22 | ```bash 23 | anchor build 24 | ``` 25 | 26 | ```bash 27 | anchor deploy 28 | ``` 29 | 30 | - To start UI 31 | ```bash 32 | cd ./frontend 33 | ``` 34 | 35 | ```bash 36 | npm run install 37 | ``` 38 | 39 | ```bash 40 | npm run start 41 | ``` 42 | 43 | 44 | 45 | ### Contact Information 46 | - Telegram: https://t.me/DevCutup 47 | - Whatsapp: https://wa.me/13137423660 48 | - Twitter: https://x.com/devcutup 49 | -------------------------------------------------------------------------------- /frontend/src/components/Piece.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDrag, DragPreviewImage } from 'react-dnd' 3 | import Image from "next/image"; 4 | 5 | 6 | interface IProps { 7 | piece: any; 8 | position: string; 9 | } 10 | 11 | export default function Piece({ 12 | piece: { type, color }, 13 | position, 14 | }: IProps) { 15 | const [{ isDragging }, drag, preview] = useDrag({ 16 | item: { 17 | type: 'piece', 18 | id: `${position}_${type}_${color}`, 19 | }, 20 | collect: (monitor) => { 21 | return { isDragging: monitor.isDragging() } 22 | }, 23 | }) 24 | 25 | // TODO: Find a way to incorporate this one 26 | // const pieceImg = require(`./assets/${type}_${color}.png`) 27 | return ( 28 | <> 29 | {/**/} 30 |
35 | chessPiece 37 |
38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | 37 | # Solana 38 | test-ledger/ 39 | 40 | #amplify-do-not-edit-begin 41 | amplify/\#current-cloud-backend 42 | amplify/.config/local-* 43 | amplify/logs 44 | amplify/mock-data 45 | amplify/backend/amplify-meta.json 46 | amplify/backend/.temp 47 | build/ 48 | dist/ 49 | node_modules/ 50 | aws-exports.js 51 | awsconfiguration.json 52 | amplifyconfiguration.json 53 | amplifyconfiguration.dart 54 | amplify-build-config.json 55 | amplify-gradle-config.json 56 | amplifytools.xcconfig 57 | .secret-* 58 | **.sample 59 | #amplify-do-not-edit-end 60 | 61 | 62 | # Idea 63 | .idea 64 | .vscode 65 | 66 | 67 | # Environment variables 68 | .env 69 | .env.devnet -------------------------------------------------------------------------------- /frontend/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useConnection, useWallet } from "@solana/wallet-adapter-react"; 2 | import type { NextPage } from "next"; 3 | import { useEffect, useState } from "react"; 4 | import toast from "react-hot-toast"; 5 | import MainBlockChessNewLayout from "../components/MainBlockChessNewLayout"; 6 | 7 | const Home: NextPage = () => { 8 | const [rpc, setRpc] = useState(null); 9 | const { connection } = useConnection(); 10 | const wallet = useWallet(); 11 | 12 | useEffect(() => { 13 | const toastConnected = async () => { 14 | if (wallet.connected) { 15 | const cluster = await connection.getClusterNodes(); 16 | if (rpc !== cluster[0].rpc) { 17 | toast(`Connected to ${cluster[0].rpc}`); 18 | setRpc(cluster[0].rpc); 19 | } 20 | } 21 | }; 22 | toastConnected(); 23 | }, [wallet, connection, rpc]); 24 | 25 | // TODO: Adding the format RPC to the network 26 | const formatRpc = rpc !== null ? `Network: ${rpc}` : ""; 27 | return ( 28 | /* 29 |
30 | 31 |
32 |
33 | 34 | */ 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default Home; 42 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | "./src/pages/**/*.{js,ts,jsx,tsx}", 4 | "./src/components/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | boxShadow: { 9 | sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)", 10 | DEFAULT: 11 | "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", 12 | md: 13 | "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", 14 | lg: 15 | "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)", 16 | xl: 17 | "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", 18 | t: "0 -1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", 19 | orange: "0px 20px 20px -15px rgba(245,56,56,0.81) ", 20 | "orange-md": "0px 20px 40px -15px rgba(245,56,56,0.81) ", 21 | none: "none", 22 | }, 23 | extend: { 24 | colors: { 25 | 'regal-blue': '#243c5a', 26 | 'square-black': '#b59963', 27 | 'square-white': '#f0d9b5' 28 | }, 29 | }, 30 | }, 31 | variants: { 32 | extend: { 33 | boxShadow: ["active", "hover"], 34 | opacity: ["disabled"], 35 | }, 36 | }, 37 | plugins: [require("@tailwindcss/forms")], 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/src/components/dialogs/PopUp.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Button from '@mui/material/Button'; 3 | import Dialog from '@mui/material/Dialog'; 4 | import DialogActions from '@mui/material/DialogActions'; 5 | import DialogContent from '@mui/material/DialogContent'; 6 | import DialogContentText from '@mui/material/DialogContentText'; 7 | import DialogTitle from '@mui/material/DialogTitle'; 8 | 9 | 10 | export const PopUp = () => { 11 | 12 | const [open, setOpen] = useState(true); 13 | 14 | const handleClose = () => { 15 | setOpen(false); 16 | }; 17 | 18 | return ( 19 | 25 | 26 | Beta - Chess2Earn.com 27 | 28 | 29 | 30 | Chess2Earn.com is currently in early beta. 31 | At the moment, it is not possible to play on phone. 32 | 33 | 34 | 35 | 38 | 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/src/components/MyChessPieces.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {FaChessKnight} from "react-icons/fa"; 3 | import {AiFillThunderbolt, AiFillTrophy} from "react-icons/ai"; 4 | import Image from "next/image"; 5 | import {ChessPiece} from "../schema/ChessPiece"; 6 | import {chessPiecesData, chessPiecesDataReverse} from "../data/chessPieces"; 7 | 8 | /** 9 | * Function to determine the background color in the list of chess pieces. 10 | * 11 | * @param index 12 | */ 13 | export function backgroundBlackOrWhite(index: number) { 14 | if (index <= 7) { 15 | return index % 2 === 0; 16 | } else { 17 | return index % 2 !== 0; 18 | } 19 | } 20 | 21 | export const MyChessPieces = ({isOpponent = false}) => { 22 | 23 | const defaultChessPieces = isOpponent ? chessPiecesDataReverse : chessPiecesData; 24 | return ( 25 | <> 26 |
27 | {defaultChessPieces.map((chessPiece, index) => ( 28 |
29 | chessPiece 30 |
31 | ) 32 | )} 33 |
34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /frontend/public/github-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Seo.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | 3 | export const Seo = ({ 4 | imgHeight = 640, 5 | imgUrl, 6 | imgWidth = 1280, 7 | pageDescription, 8 | path, 9 | title, 10 | }: { 11 | imgHeight: number; 12 | imgUrl: string; 13 | imgWidth: number; 14 | pageDescription: string; 15 | path: string; 16 | title: string; 17 | }) => { 18 | const defaultDescription = ""; 19 | 20 | const description = pageDescription ? pageDescription : defaultDescription; 21 | 22 | return ( 23 | 24 | {title} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/public/Icon/bx_bxs-server.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/Icon/bx_bxs-server.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /programs/chess/src/instruction.rs: -------------------------------------------------------------------------------- 1 | // use core::num; 2 | 3 | use solana_program::{ 4 | program_error::ProgramError, 5 | }; 6 | use crate::error::EscrowError::InvalidInstruction; 7 | 8 | pub enum EscrowInstruction { 9 | InitEscrow { 10 | is_cretor: u8, 11 | amount: u64, 12 | }, 13 | 14 | WithdrawEscrow { 15 | result: u8, 16 | amount: u64, 17 | } 18 | } 19 | 20 | impl EscrowInstruction { 21 | /// Unpacks a byte buffer into a [EscrowInstruction](enum.EscrowInstruction.html). 22 | pub fn unpack(input: &[u8]) -> Result { 23 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 24 | 25 | Ok(match tag { 26 | 0 => { 27 | let (is_cretor, amount) = rest.split_first().ok_or(InvalidInstruction)?; 28 | Self::InitEscrow { 29 | is_cretor: *is_cretor, 30 | amount: Self::unpack_amount(amount)? 31 | } 32 | }, 33 | 1 => { 34 | let (result, amount) = rest.split_first().ok_or(InvalidInstruction)?; 35 | Self::WithdrawEscrow { 36 | result: *result, 37 | amount: Self::unpack_amount(amount)?, 38 | } 39 | }, 40 | _ => return Err(InvalidInstruction.into()), 41 | }) 42 | } 43 | 44 | fn unpack_amount(input: &[u8]) -> Result { 45 | let amount = input 46 | .get(..8) 47 | .and_then(|slice| slice.try_into().ok()) 48 | .map(u64::from_le_bytes) 49 | .ok_or(InvalidInstruction)?; 50 | Ok(amount) 51 | } 52 | } -------------------------------------------------------------------------------- /frontend/src/components/BoardSquare.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Square from './Square' 3 | import Piece from './Piece' 4 | import { useDrop } from 'react-dnd' 5 | import { handleMove } from './Game' 6 | import { gameSubject } from './Game' 7 | import Promote from './Promote' 8 | 9 | 10 | interface IProps { 11 | piece: string; 12 | black: boolean; 13 | position: string; 14 | mySecondsRemaining: number; 15 | } 16 | /** 17 | * The board square object. 18 | * 19 | * @param piece 20 | * @param black 21 | * @param position 22 | * @param mySecondsRemaining 23 | * @returns {JSX.Element} 24 | * @constructor 25 | */ 26 | export default function BoardSquare({ 27 | piece, 28 | black, 29 | position, 30 | mySecondsRemaining 31 | }: IProps) { 32 | const [promotion, setPromotion] = useState(null) 33 | const [, drop] = useDrop({ 34 | accept: 'piece', 35 | drop: (item: any) => { 36 | const [fromPosition] = item.id.split('_') 37 | handleMove(fromPosition, position, mySecondsRemaining) 38 | }, 39 | }) 40 | useEffect(() => { 41 | const subscribe = gameSubject?.subscribe( 42 | ({ pendingPromotion }: any) => 43 | pendingPromotion && pendingPromotion.to === position 44 | ? setPromotion(pendingPromotion) 45 | : setPromotion(null) 46 | ) 47 | return () => subscribe.unsubscribe() 48 | }, [position]) 49 | return ( 50 |
51 | 52 | {promotion ? ( 53 | 54 | ) : piece ? ( 55 | 56 | ) : null} 57 | 58 |
59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/components/Board.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import BoardSquare from './BoardSquare' 3 | 4 | 5 | interface IProps { 6 | board: any; 7 | position: any; 8 | mySecondsRemaining: number; 9 | } 10 | 11 | /** 12 | * The board game object. 13 | * 14 | * @param board 15 | * @param position 16 | * @param mySecondsRemaining: the remaining seconds to lose of the player. 17 | * @returns {JSX.Element} 18 | * @constructor 19 | */ 20 | export default function Board({ board, position, mySecondsRemaining }: IProps) { 21 | const [currBoard, setCurrBoard] = useState([]) 22 | 23 | useEffect(() => { 24 | setCurrBoard( 25 | position === 'w' ? board.flat() : board.flat().reverse() 26 | ) 27 | }, [board, position]) 28 | 29 | function getXYPosition(i: number) { 30 | const x = position === 'w' ? i % 8 : Math.abs((i % 8) - 7) 31 | const y = 32 | position === 'w' 33 | ? Math.abs(Math.floor(i / 8) - 7) 34 | : Math.floor(i / 8) 35 | return { x, y } 36 | } 37 | 38 | function isBlack(i: number) { 39 | const { x, y } = getXYPosition(i) 40 | return (x + y) % 2 === 1 41 | } 42 | 43 | function getPosition(i: number) { 44 | const { x, y } = getXYPosition(i) 45 | const letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][ 46 | x 47 | ] 48 | return `${letter}${y + 1}` 49 | } 50 | return ( 51 |
52 | {currBoard.map((piece, i) => ( 53 |
54 | 60 |
61 | ))} 62 |
63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chess2earn-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "clean": "shx rm -rf .next", 7 | "dev-mainnet": "next dev", 8 | "dev-devnet": "env-cmd -f .env.devnet next dev", 9 | "build": "next build", 10 | "start": "next start", 11 | "lint": "next lint" 12 | }, 13 | "engines": { 14 | "node": ">=14.15.0", 15 | "npm": ">=7.20.0" 16 | }, 17 | "dependencies": { 18 | "@emotion/react": "^11.8.1", 19 | "@emotion/styled": "^11.8.1", 20 | "@headlessui/react": "^1.4.1", 21 | "@mui/material": "^5.4.4", 22 | "@tailwindcss/forms": "^0.3.4", 23 | "chess.js": "^0.12.1", 24 | "env-cmd": "^10.1.0", 25 | "firebase": "^8.7.0", 26 | "next": "^12.2.2", 27 | "play2earn": "^1.0.15", 28 | "react": "17.0.2", 29 | "react-dnd": "^11.1.3", 30 | "react-dnd-html5-backend": "^11.1.3", 31 | "react-dom": "17.0.2", 32 | "react-firebase-hooks": "^3.0.4", 33 | "react-hot-toast": "^2.1.1", 34 | "react-icons": "^4.3.1", 35 | "react-loader-spinner": "^6.0.0-0", 36 | "react-router-dom": "^5.2.0", 37 | "react-scroll": "^1.8.6", 38 | "react-share": "^4.4.0", 39 | "react-toastify": "^8.2.0", 40 | "rxfire": "^4.0.0", 41 | "rxjs": "^7.1.0", 42 | "superstruct": "^0.15.2" 43 | }, 44 | "devDependencies": { 45 | "@types/react": "17.0.25", 46 | "autoprefixer": "10.4.5", 47 | "eslint": "7.32.0", 48 | "eslint-config-next": "11.1.2", 49 | "next-transpile-modules": "^8.0.0", 50 | "postcss": "^8.3.8", 51 | "shx": "^0.3.3", 52 | "tailwindcss": "^2.2.16", 53 | "typescript": "4.4.3" 54 | }, 55 | "overrides": { 56 | "autoprefixer": "10.4.5" 57 | }, 58 | "author": "Marin Bouthemy", 59 | "license": "MIT" 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/contexts/LoadingContext.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createContext, 3 | Dispatch, 4 | ReactNode, 5 | useContext, 6 | useReducer, 7 | } from "react"; 8 | 9 | type State = { show: boolean } | undefined; 10 | type Action = { type: "SHOW_LOADING" } | { type: "HIDE_LOADING" }; 11 | 12 | const LoadingStateContext = createContext(undefined); 13 | const LoadingDispatchContext = createContext | undefined>( 14 | undefined 15 | ); 16 | 17 | const reducer = (state: State, action: Action): State => { 18 | switch (action?.type) { 19 | case "SHOW_LOADING": 20 | return { 21 | show: true, 22 | }; 23 | case "HIDE_LOADING": 24 | return { 25 | show: false, 26 | }; 27 | default: 28 | return { 29 | show: false, 30 | }; 31 | } 32 | }; 33 | 34 | const initialState = { 35 | show: false, 36 | }; 37 | 38 | export const LoadingProvider = ({ children }: { children: ReactNode }) => { 39 | const [state, dispatch] = useReducer(reducer, initialState); 40 | 41 | return ( 42 | 43 | 44 | {children} 45 | 46 | 47 | ); 48 | }; 49 | 50 | export const useLoadingState = () => { 51 | const state = useContext(LoadingStateContext); 52 | if (state === undefined) { 53 | throw new Error("useLoadingState should be used with LoadingProvider"); 54 | } 55 | return state; 56 | }; 57 | 58 | export const useLoadingDispatch = () => { 59 | const dispatch = useContext(LoadingDispatchContext); 60 | 61 | if (dispatch === undefined) { 62 | throw new Error("useLoadingDispatch should be used with LoadingProvider"); 63 | } 64 | return dispatch; 65 | }; 66 | -------------------------------------------------------------------------------- /frontend/src/components/SharingButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {FaRocket} from "react-icons/fa"; 3 | import {toast} from "react-toastify"; 4 | 5 | interface IProps { 6 | shareableLink: string; 7 | } 8 | 9 | /** 10 | * Sharing button for the link. 11 | * @constructor 12 | */ 13 | export const SharingButton = ({shareableLink}: IProps) => { 14 | 15 | 16 | /** 17 | * Copy the link to the clipboard. 18 | */ 19 | async function copyToClipboard() { 20 | await navigator.clipboard.writeText(shareableLink); 21 | toast.success('Link copied!', { 22 | position: "top-right", 23 | autoClose: 3000, 24 | hideProgressBar: true, 25 | closeOnClick: true, 26 | draggable: true, 27 | progress: undefined, 28 | }); 29 | } 30 | 31 | return ( 32 |
33 |
34 | 35 |

36 | Share this game to continue. 37 | Post the link in the Discord to find players. 38 |

39 | 40 | 47 |
48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/components/Wallet.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useMemo } from "react"; 2 | import { 3 | ConnectionProvider, 4 | WalletProvider, 5 | } from "@solana/wallet-adapter-react"; 6 | import { WalletAdapterNetwork, WalletError } from "@solana/wallet-adapter-base"; 7 | import { 8 | getLedgerWallet, 9 | getPhantomWallet, 10 | getSlopeWallet, 11 | getSolflareWallet, 12 | } from "@solana/wallet-adapter-wallets"; 13 | import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; 14 | import { clusterApiUrl } from "@solana/web3.js"; 15 | 16 | // Default styles that can be overridden by your app 17 | require("@solana/wallet-adapter-react-ui/styles.css"); 18 | 19 | interface Props { 20 | children: React.ReactNode | React.ReactNode[]; 21 | network: WalletAdapterNetwork; 22 | localAddress?: string; 23 | } 24 | 25 | const WalletConnectionProvider: FC = ({ 26 | children, 27 | localAddress, 28 | network, 29 | }: Props) => { 30 | const endpoint = useMemo(() => { 31 | if (localAddress) { 32 | return localAddress; 33 | } 34 | return clusterApiUrl(network); 35 | }, [localAddress, network]); 36 | 37 | // @solana/wallet-adapter-wallets includes all the adapters but supports tree shaking -- 38 | // Only the wallets you configure here will be compiled into your application 39 | const wallets = useMemo( 40 | () => [ 41 | getPhantomWallet(), 42 | getSlopeWallet(), 43 | getSolflareWallet(), 44 | getLedgerWallet(), 45 | ], 46 | [] 47 | ); 48 | 49 | const onError = (error: WalletError) => { 50 | // toast(error.message ? `${error.name}: ${error.message}` : error.name); 51 | console.error(error); 52 | }; 53 | return ( 54 | 55 | 56 | {children} 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default WalletConnectionProvider; 63 | -------------------------------------------------------------------------------- /frontend/src/components/LayoutLanding/FooterLanding.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | const FooterLanding = () => { 5 | return ( 6 |
7 |
8 |
9 |

10 | Chess2Earn is the first chess game on the Solana Blockchain

11 |
12 |
13 |

©2022 - Chess2Earn

14 |
15 |
16 |

Product

17 | 26 |
27 |
28 |

Engage

29 | 38 |
39 |
40 |
41 | ) 42 | }; 43 | 44 | export default FooterLanding; -------------------------------------------------------------------------------- /frontend/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic"; 2 | import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"; 3 | import "../styles/globals.css"; 4 | import type { AppProps } from "next/app"; 5 | import { LoadingProvider } from "../contexts/LoadingContext"; 6 | import { ModalProvider } from "../contexts/ModalContext"; 7 | import React from "react"; 8 | import { Seo } from "../components/Seo"; 9 | import '../styles.css'; 10 | import { DndProvider } from 'react-dnd' 11 | import { HTML5Backend } from 'react-dnd-html5-backend' 12 | import {RouteGuard} from "../contexts/RouteGuarding"; 13 | 14 | const WalletConnectionProvider = dynamic(() => import("../components/Wallet"), { 15 | ssr: false, 16 | }); 17 | 18 | const network = () => { 19 | switch (process.env.NEXT_PUBLIC_BUILD_ENV) { 20 | case "dev": 21 | return WalletAdapterNetwork.Devnet; 22 | case "prod": 23 | return WalletAdapterNetwork.Mainnet; 24 | default: 25 | return WalletAdapterNetwork.Devnet; 26 | } 27 | }; 28 | 29 | function Chess2EarnApp({ Component, pageProps }: AppProps) { 30 | const localAddress = process.env.NEXT_PUBLIC_LOCAL_ADDRESS; 31 | return ( 32 | <> 33 | 41 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | export default Chess2EarnApp; 59 | -------------------------------------------------------------------------------- /frontend/src/contexts/RouteGuarding.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import {useAuthState} from "react-firebase-hooks/auth"; 4 | import {auth} from "../firebase"; 5 | 6 | export { RouteGuard }; 7 | 8 | // https://jasonwatmore.com/post/2021/08/30/next-js-redirect-to-login-page-if-unauthenticated 9 | function RouteGuard({ children }: any) { 10 | 11 | const [user, loading, error] = useAuthState(auth) 12 | const router = useRouter(); 13 | const [authorized, setAuthorized] = useState(false); 14 | 15 | useEffect(() => { 16 | // on initial load - run auth check 17 | authCheck(router.asPath); 18 | 19 | // on route change start - hide page content by setting authorized to false 20 | const hideContent = () => setAuthorized(false); 21 | router.events.on('routeChangeStart', hideContent); 22 | 23 | // on route change complete - run auth check 24 | router.events.on('routeChangeComplete', authCheck) 25 | 26 | // unsubscribe from events in useEffect return function 27 | return () => { 28 | router.events.off('routeChangeStart', hideContent); 29 | router.events.off('routeChangeComplete', authCheck); 30 | } 31 | 32 | // eslint-disable-next-line react-hooks/exhaustive-deps 33 | }, [loading, user]); 34 | 35 | function authCheck(url: string) { 36 | // redirect to login page if accessing a private page and not logged in 37 | const publicPaths = ['/signin']; 38 | const path = url.split('?')[0]; 39 | if (!loading) { 40 | if (typeof user?.uid ==='undefined' && !publicPaths.includes(path)) { 41 | setAuthorized(false); 42 | router.push({ 43 | pathname: '/signin', 44 | query: { returnUrl: router.asPath } 45 | }); 46 | } else { 47 | setAuthorized(true); 48 | } 49 | } 50 | } 51 | 52 | return (authorized && children); 53 | } 54 | -------------------------------------------------------------------------------- /programs/chess/src/state.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | program_error::ProgramError, 3 | program_pack::{IsInitialized, Pack, Sealed}, 4 | pubkey::Pubkey, 5 | }; 6 | use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; 7 | 8 | pub struct Escrow { 9 | pub is_initialized: bool, 10 | pub creator_pubkey: Pubkey, 11 | pub competitor_pubkey: Pubkey, 12 | pub amount: u64, 13 | } 14 | 15 | impl Sealed for Escrow {} 16 | 17 | impl IsInitialized for Escrow { 18 | fn is_initialized(&self) -> bool { 19 | self.is_initialized 20 | } 21 | } 22 | 23 | impl Pack for Escrow { 24 | const LEN: usize = 73; 25 | fn unpack_from_slice(src: &[u8]) -> Result { 26 | let src = array_ref![src, 0, Escrow::LEN]; 27 | let ( 28 | is_initialized, 29 | creator_pubkey, 30 | competitor_pubkey, 31 | amount, 32 | ) = array_refs![src, 1, 32, 32, 8]; 33 | let is_initialized = match is_initialized { 34 | [0] => false, 35 | [1] => true, 36 | _ => return Err(ProgramError::InvalidAccountData), 37 | }; 38 | 39 | Ok(Escrow { 40 | is_initialized, 41 | creator_pubkey: Pubkey::new_from_array(*creator_pubkey), 42 | competitor_pubkey: Pubkey::new_from_array(*competitor_pubkey), 43 | amount: u64::from_le_bytes(*amount), 44 | }) 45 | } 46 | 47 | fn pack_into_slice(&self, dst: &mut [u8]) { 48 | let dst = array_mut_ref![dst, 0, Escrow::LEN]; 49 | let ( 50 | is_initialized_dst, 51 | creator_pubkey_dst, 52 | competitor_pubkey_dst, 53 | amount_dst, 54 | ) = mut_array_refs![dst, 1, 32, 32, 8]; 55 | 56 | let Escrow { 57 | is_initialized, 58 | creator_pubkey, 59 | competitor_pubkey, 60 | amount, 61 | } = self; 62 | 63 | is_initialized_dst[0] = *is_initialized as u8; 64 | creator_pubkey_dst.copy_from_slice(creator_pubkey.as_ref()); 65 | competitor_pubkey_dst.copy_from_slice(competitor_pubkey.as_ref()); 66 | *amount_dst = amount.to_le_bytes(); 67 | } 68 | } -------------------------------------------------------------------------------- /frontend/src/components/DescriptionGame.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {FaChessKnight} from "react-icons/fa"; 3 | import {AiFillThunderbolt, AiFillTrophy} from "react-icons/ai"; 4 | 5 | /** 6 | * Function to determine the background color in the list of chess pieces. 7 | * 8 | * @param index 9 | */ 10 | export function backgroundBlackOrWhite(index: number) { 11 | if (index <= 7) { 12 | return index % 2 === 0; 13 | } else { 14 | return index % 2 !== 0; 15 | } 16 | } 17 | 18 | export const DescriptionGame = () => { 19 | 20 | return ( 21 | <> 22 | 23 | {/* Explanation Card Container */} 24 |
25 |
26 |
27 | 28 |

1. Start a game and share it

To start playing Chess2Earn, create a new game and invite an opponent.

30 |
31 | 32 |

2. Bet some Solana

Start a game against another player. 34 | Before playing, both of you need to bet.

35 |
36 | 37 |

3. The winner takes it 38 | all

The winner get the Solana bet.

40 |
41 |
42 |
43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/contexts/ModalContext.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createContext, 3 | Dispatch, 4 | ReactNode, 5 | useContext, 6 | useReducer, 7 | } from "react"; 8 | 9 | export enum ModalUserAction { 10 | CancelOffer, 11 | AcceptOffer, 12 | } 13 | 14 | export type ActionProps = 15 | | { 16 | type: ModalUserAction.CancelOffer; 17 | id: string; 18 | escrowAddress: string; 19 | nftAddress: string; 20 | } 21 | | { 22 | type: ModalUserAction.AcceptOffer; 23 | id: string; 24 | amount: number; 25 | escrowAddress: string; 26 | nftAddress: string; 27 | }; 28 | type Content = { 29 | buttonName: string; 30 | message: string; 31 | props: ActionProps; 32 | title: string; 33 | }; 34 | type State = { content?: Content | undefined; show: boolean } | undefined; 35 | type Action = { type: "SHOW_DIALOG"; input: Content } | { type: "HIDE_DIALOG" }; 36 | 37 | const ModalStateContext = createContext(undefined); 38 | const ModalDispatchContext = createContext | undefined>( 39 | undefined 40 | ); 41 | 42 | const modalReducer = (state: State, action: Action): State => { 43 | switch (action?.type) { 44 | case "SHOW_DIALOG": 45 | const content: Content = { 46 | ...action.input, 47 | }; 48 | return { 49 | content, 50 | show: true, 51 | }; 52 | case "HIDE_DIALOG": 53 | return { 54 | content: undefined, 55 | show: false, 56 | }; 57 | default: 58 | return { 59 | ...state, 60 | show: false, 61 | }; 62 | } 63 | }; 64 | 65 | const initialState = { 66 | show: false, 67 | }; 68 | 69 | export const ModalProvider = ({ children }: { children: ReactNode }) => { 70 | const [state, dispatch] = useReducer(modalReducer, initialState); 71 | 72 | return ( 73 | 74 | 75 | {children} 76 | 77 | 78 | ); 79 | }; 80 | 81 | export const useModalState = () => { 82 | const state = useContext(ModalStateContext); 83 | if (state === undefined) { 84 | throw new Error("useModalState should be used with ModalProvider"); 85 | } 86 | return state; 87 | }; 88 | 89 | export const useModalDispatch = () => { 90 | const dispatch = useContext(ModalDispatchContext); 91 | 92 | if (dispatch === undefined) { 93 | throw new Error("useModalDispatch should be used with ModalProvider"); 94 | } 95 | return dispatch; 96 | }; 97 | -------------------------------------------------------------------------------- /frontend/src/components/HeaderOriginal.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | 4 | export const HeaderOriginal = () => { 5 | 6 | 7 | return ( 8 |
9 |
10 | 11 | 12 |
13 | Chess2Earn.com 14 |
15 |
16 | 17 |
18 | Chess on Solana. 19 |
20 |
21 |
22 | 23 | {/*The Discord button.*/} 24 | 38 |
39 |
40 | ) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /frontend/src/data/chessPieces.ts: -------------------------------------------------------------------------------- 1 | import {ChessPiece} from "../schema/ChessPiece"; 2 | 3 | 4 | export const chessPiecesData: ChessPiece[] = [ 5 | {name: 'pawn', id: 'pawn_0', imageURL: '/p_w.png', isMissing: true}, 6 | {name: 'pawn', id: 'pawn_1', imageURL: '/p_w.png', isMissing: true}, 7 | {name: 'pawn', id: 'pawn_2', imageURL: '/p_w.png', isMissing: true}, 8 | {name: 'pawn', id: 'pawn_3', imageURL: '/p_w.png', isMissing: true}, 9 | {name: 'pawn', id: 'pawn_4', imageURL: '/p_w.png', isMissing: true}, 10 | {name: 'pawn', id: 'pawn_5', imageURL: '/p_w.png', isMissing: true}, 11 | {name: 'pawn', id: 'pawn_6', imageURL: '/p_w.png', isMissing: true}, 12 | {name: 'pawn', id: 'pawn_7', imageURL: '/p_w.png', isMissing: true}, 13 | {name: 'rook', id: 'rook_0', imageURL: '/r_w.png', isMissing: true}, 14 | {name: 'knight', id: 'knight_0', imageURL: '/n_w.png', isMissing: true}, 15 | {name: 'bishop', id: 'bishop_0', imageURL: '/b_w.png', isMissing: true}, 16 | {name: 'queen', id: 'queen_0', imageURL: '/q_w.png', isMissing: true}, 17 | {name: 'king', id: 'king_0', imageURL: '/k_w.png', isMissing: true}, 18 | {name: 'bishop', id: 'bishop_0', imageURL: '/b_w.png', isMissing: true}, 19 | {name: 'knight', id: 'knight_0', imageURL: '/n_w.png', isMissing: true}, 20 | {name: 'rook', id: 'rook_0', imageURL: '/r_w.png', isMissing: true}, 21 | ]; 22 | 23 | 24 | export const chessPiecesDataReverse: ChessPiece[] = [ 25 | {name: 'rook', id: 'rook_0', imageURL: '/r_b.png', isMissing: false}, 26 | {name: 'knight', id: 'knight_0', imageURL: '/n_b.png', isMissing: false}, 27 | {name: 'bishop', id: 'bishop_0', imageURL: '/b_b.png', isMissing: false}, 28 | {name: 'king', id: 'king_0', imageURL: '/k_b.png', isMissing: false}, 29 | {name: 'queen', id: 'queen_0', imageURL: '/q_b.png', isMissing: false}, 30 | {name: 'bishop', id: 'bishop_0', imageURL: '/b_b.png', isMissing: false}, 31 | {name: 'knight', id: 'knight_0', imageURL: '/n_b.png', isMissing: false}, 32 | {name: 'rook', id: 'rook_0', imageURL: '/r_b.png', isMissing: false}, 33 | {name: 'pawn', id: 'pawn_0', imageURL: '/p_b.png', isMissing: false}, 34 | {name: 'pawn', id: 'pawn_1', imageURL: '/p_b.png', isMissing: false}, 35 | {name: 'pawn', id: 'pawn_2', imageURL: '/p_b.png', isMissing: false}, 36 | {name: 'pawn', id: 'pawn_3', imageURL: '/p_b.png', isMissing: false}, 37 | {name: 'pawn', id: 'pawn_4', imageURL: '/p_b.png', isMissing: false}, 38 | {name: 'pawn', id: 'pawn_5', imageURL: '/p_b.png', isMissing: false}, 39 | {name: 'pawn', id: 'pawn_6', imageURL: '/p_b.png', isMissing: false}, 40 | {name: 'pawn', id: 'pawn_7', imageURL: '/p_b.png', isMissing: false} 41 | ]; 42 | -------------------------------------------------------------------------------- /frontend/src/components/dialogs/ModalDialog.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog } from "@headlessui/react"; 2 | import { 3 | ActionProps, 4 | useModalDispatch, 5 | useModalState, 6 | } from "../../contexts/ModalContext"; 7 | 8 | interface DialogProps { 9 | onConfirm: (props: ActionProps) => any; 10 | } 11 | export const ModalDialog = ({ onConfirm }: DialogProps) => { 12 | const dispatch = useModalDispatch(); 13 | const state = useModalState(); 14 | 15 | const handleClose = () => { 16 | dispatch({ type: "HIDE_DIALOG" }); 17 | }; 18 | 19 | const handleConfirm = (props: ActionProps) => async () => { 20 | await onConfirm(props); 21 | dispatch({ type: "HIDE_DIALOG" }); 22 | }; 23 | 24 | return ( 25 | 30 |
31 | 32 | {state.content && ( 33 |
34 |
35 |
36 | 37 | {state.content.title} 38 | 39 | 40 | {state.content.message} 41 | 42 |
43 |
44 | 50 | 56 |
57 |
58 |
59 |
60 |
61 | )} 62 |
63 |
64 | ); 65 | }; 66 | -------------------------------------------------------------------------------- /frontend/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 31 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | /* background-color: rgb(245 245 245); */ 3 | background-color: white; 4 | font-family: Nexa, sans-serif; 5 | } 6 | 7 | .app-container { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | margin-top: 50px; 13 | } 14 | 15 | .textHeader { 16 | margin-top: 20px; 17 | font-size: 34px; 18 | font-weight: 700; 19 | } 20 | 21 | .title-presentation { 22 | font-size: 22px; 23 | font-weight: 700; 24 | margin-top: 10px; 25 | } 26 | 27 | .dividerBlue { 28 | width: 100px; 29 | height: 4px; 30 | border-radius: 12px; 31 | margin-bottom: 10px; 32 | background: #b59963; 33 | } 34 | 35 | .pointer { 36 | cursor: pointer; 37 | } 38 | 39 | .share-game { 40 | position: absolute; 41 | width: 500px; 42 | bottom: 0; 43 | left: 0; 44 | } 45 | .board-container { 46 | width: 600px; 47 | height: 600px; 48 | } 49 | 50 | .board { 51 | width: 100%; 52 | height: 100%; 53 | display: flex; 54 | flex-wrap: wrap; 55 | } 56 | 57 | .square { 58 | width: 12.5%; 59 | height: 12.5%; 60 | } 61 | 62 | .promote-square { 63 | width: 50%; 64 | height: 50%; 65 | } 66 | 67 | .square-black { 68 | background: #b59963; 69 | } 70 | 71 | .square-white { 72 | background: #f0d9b5; 73 | } 74 | 75 | .board-square { 76 | width: 100%; 77 | height: 100%; 78 | } 79 | 80 | .cursor-pointer { 81 | cursor: pointer; 82 | } 83 | 84 | .piece-container { 85 | cursor: grab; 86 | width: 100%; 87 | height: 100%; 88 | display: flex; 89 | align-items: center; 90 | justify-content: center; 91 | } 92 | 93 | .piece { 94 | max-width: 70%; 95 | max-height: 70%; 96 | } 97 | 98 | .vertical-text { 99 | text-orientation: upright; 100 | writing-mode: vertical-lr; 101 | font-family: sans-serif; 102 | padding: 10px; 103 | } 104 | 105 | .vertical-text button { 106 | margin-top: 20px; 107 | cursor: pointer; 108 | background: rgb(63, 63, 63); 109 | border: 2px solid white; 110 | border-radius: 10px; 111 | } 112 | 113 | .iconTitle { 114 | width: 25px; 115 | height: 25px; 116 | } 117 | 118 | .linkDiscord { 119 | color: white; 120 | font-weight: bold; 121 | border-radius: 8px; 122 | display: inline-flex; 123 | align-items: center; 124 | padding: 10px 15px; 125 | background-color: #7289da; 126 | text-decoration: none; 127 | } 128 | 129 | .linkDiscord:hover { 130 | background-color: #6a7fc9; 131 | } 132 | 133 | .discordContainer { 134 | margin-top: 30px; 135 | margin-bottom: 30px; 136 | } 137 | 138 | @media screen and (max-width: 600px) { 139 | .my-chess-pieces { 140 | visibility: hidden; 141 | display: none; 142 | } 143 | } 144 | 145 | 146 | 147 | .aboutCreators { 148 | display: flex; 149 | flex-direction: row; 150 | justify-content: center; 151 | gap: 100px; 152 | margin-top: 20px; 153 | } 154 | 155 | .creatorName { 156 | font-size: 18px; 157 | font-weight: bold; 158 | } 159 | 160 | .horizontalLine { 161 | color: #f5f3f3; 162 | background-color: #f5f3f3; 163 | height: 1px; 164 | } 165 | 166 | .titleIntermediate { 167 | font-size: 24px; 168 | font-style: italic; 169 | margin-top: 40px; 170 | color: #f5f3f3; 171 | } -------------------------------------------------------------------------------- /frontend/src/pages/signin.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useRouter } from "next/dist/client/router"; 4 | import { auth } from '../firebase' 5 | import { backgroundBlackOrWhite, DescriptionGame } from "../components/DescriptionGame"; 6 | import { MyChessPieces } from "../components/MyChessPieces"; 7 | import { useAuthState } from "react-firebase-hooks/auth"; 8 | import LayoutLanding from "../components/LayoutLanding/LayoutLanding"; 9 | 10 | const SignIn: NextPage = () => { 11 | const [user, _, _error] = useAuthState(auth) 12 | const router = useRouter(); 13 | 14 | const [userName, setUserName] = useState(''); 15 | 16 | const handleChangeUsername = (e: React.ChangeEvent) => { 17 | setUserName(e.target.value ? e.target.value.toString() : ""); 18 | }; 19 | 20 | /** 21 | * Sign in the username with Firebase. 22 | * @param e 23 | */ 24 | const handleSignIn = async (e: React.MouseEvent) => { 25 | e.preventDefault(); 26 | // TODO: Save the username in Firebase when sign in 27 | localStorage.setItem('userName', userName); 28 | await auth.signInAnonymously(); 29 | }; 30 | 31 | useEffect(() => { 32 | if (user) { 33 | // Return to the previous page. 34 | const returnUrl = router.query.returnUrl || '/'; 35 | 36 | // @ts-ignore 37 | router.push(returnUrl); 38 | } 39 | }, [user, router]) 40 | 41 | 42 | return ( 43 | 44 | 45 | {/* Play Section */} 46 | 47 |
48 |

Play Chess

49 |
50 |
51 | 52 |
53 | {/* The form to sign in and register. */} 54 |
55 |
56 |
57 | 61 |
62 | 71 |
72 | 78 |
79 | 80 |
81 | 82 | 83 | 84 |
85 | 86 |
87 | 88 |
89 |

How to get started

90 |
91 | 92 | 93 |
94 | 95 | 96 | 97 | ); 98 | }; 99 | 100 | export default SignIn; 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /frontend/src/components/MainBlockChessNewLayout.tsx: -------------------------------------------------------------------------------- 1 | // Test the landing page 2 | import React, { useEffect, useState } from "react"; 3 | import LayoutLanding from "./LayoutLanding/LayoutLanding"; 4 | import { useRouter } from "next/dist/client/router"; 5 | import { useAuthState } from "react-firebase-hooks/auth"; 6 | import { TailSpin } from "react-loader-spinner"; 7 | import { auth, db } from "../firebase"; 8 | import { defaultName } from "../data/parameters"; 9 | import { Member } from "../schema/Member"; 10 | import { DescriptionGame } from "./DescriptionGame"; 11 | import { MyChessPieces } from "./MyChessPieces"; 12 | import { GameListTable } from "./GameListTable"; 13 | import { ToastContainer } from 'react-toastify'; 14 | import 'react-toastify/dist/ReactToastify.css'; 15 | 16 | 17 | // TODO: Do a huge refactoring of the different components 18 | // TODO: Do a huge refactoring of all the style 19 | const MainBlockChessNewLayout = () => { 20 | const router = useRouter(); 21 | 22 | const [user, _, _error] = useAuthState(auth) 23 | 24 | const [isGameLoading, setIsGameLoading] = useState(false); 25 | 26 | 27 | /** 28 | * Create a new game and move to the new screen. 29 | */ 30 | const startGame = async () => { 31 | setIsGameLoading(true); 32 | const member: Member = { 33 | uid: user?.uid, 34 | piece: ['b', 'w'][Math.round(Math.random())], 35 | name: localStorage.getItem('userName') || defaultName, 36 | creator: true, 37 | hasBetChessPiece: false 38 | } 39 | 40 | const dateNow = Date.now(); 41 | const game = { 42 | status: 'waiting', 43 | player1: member, 44 | members: [member], 45 | gameCreatedAt: dateNow, 46 | gameId: `${Math.random().toString(36).substr(2, 9)}_${dateNow}` 47 | } 48 | 49 | await db.collection('games').doc(game.gameId).set(game) 50 | router.push(`/game/${game.gameId}`) 51 | } 52 | 53 | 54 | /** 55 | * Sign out of Firebase, mostly used for debugging. 56 | */ 57 | const handleSignOut = () => { 58 | auth.signOut(); 59 | } 60 | 61 | 62 | 63 | return ( 64 | <> 65 | 66 | 67 | 68 | 69 | 70 | {/* Play Section */} 71 | 72 |
73 |

Play Chess

74 |
75 |
76 |
77 | 78 | 84 | {isGameLoading && 85 | 86 | } 87 | 88 |
89 | 90 | 91 | 92 |
93 | 94 | 95 | {/* The table of games. */} 96 | 97 | 98 | 99 | 100 |
101 | 102 |
103 |

How to get started

104 |
105 | 106 | 107 | 108 |
109 | 110 | 111 | 112 | 113 | 114 | ); 115 | } 116 | 117 | 118 | export default MainBlockChessNewLayout; 119 | -------------------------------------------------------------------------------- /frontend/src/components/GameListTable.tsx: -------------------------------------------------------------------------------- 1 | import Table from '@mui/material/Table'; 2 | import TableBody from '@mui/material/TableBody'; 3 | import TableCell from '@mui/material/TableCell'; 4 | import TableHead from '@mui/material/TableHead'; 5 | import TableRow from '@mui/material/TableRow'; 6 | import {useState} from 'react'; 7 | import {GameInformation} from '../schema/gameInformation'; 8 | import Link from "next/link"; 9 | import {db} from "../firebase"; 10 | 11 | /** 12 | * The table of the started games that the user can join. 13 | */ 14 | export const GameListTable = () => { 15 | 16 | const [gameList, setGameList] = useState([]); 17 | 18 | function computeTimeDifferenceMinutes(date1: number, date2: number): number { 19 | const difference = date2 - date1; // This will give difference in milliseconds 20 | return Math.round(difference / 60000); 21 | } 22 | 23 | 24 | /** 25 | * Query all games already started. 26 | */ 27 | const getGamesStarted = () => { 28 | db.collection("games").where("status", "==", "waiting").orderBy('gameCreatedAt', 'desc').limit(15) 29 | .get() 30 | .then((querySnapshot: any) => { 31 | const dateNow = Date.now(); 32 | const gameListFb: GameInformation[] = querySnapshot.docs.map((doc: any) => ({ 33 | gameId: doc.id, 34 | gameCreatedAt: computeTimeDifferenceMinutes(doc.data().gameCreatedAt || dateNow, dateNow), 35 | status: doc.data().status 36 | })); 37 | setGameList(gameListFb); 38 | }) 39 | .catch((error) => { 40 | console.log("Error getting documents: ", error); 41 | }); 42 | } 43 | 44 | return ( 45 | <> 46 | 51 | 52 | 53 | 54 | 55 | Game ID 56 | Created 57 | Status 58 | Join Game 59 | 60 | 61 | 62 | {gameList.map((row: GameInformation) => ( 63 | 66 | 67 | {row.gameId} 68 | 69 | {row.gameCreatedAt} min ago 70 | Waiting 71 | 72 | 73 | 77 | 78 | 79 | 80 | ))} 81 | 82 |
83 | 84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /frontend/src/components/Game.ts: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import {map} from 'rxjs/operators'; 4 | import {auth} from '../firebase'; 5 | import {fromDocRef} from 'rxfire/firestore'; 6 | // @ts-ignore 7 | import * as ChessJS from 'chess.js'; 8 | import {gameStatusEnum} from "../schema/enums"; 9 | import {Member} from "../schema/Member"; 10 | import {defaultName, NUMBER_SECONDS_GAME} from "../data/parameters"; 11 | import firebase from "firebase"; 12 | 13 | let gameRef: any; 14 | let member: any; 15 | 16 | const Chess = typeof ChessJS === "function" ? ChessJS : ChessJS.Chess; 17 | 18 | const chess = new Chess(); 19 | 20 | 21 | export let gameSubject: any; 22 | 23 | export async function initGame(gameRefFb: any) { 24 | 25 | const {currentUser} = auth 26 | if (gameRefFb) { 27 | gameRef = gameRefFb; 28 | const initialGame = await gameRefFb.get().then((doc: any) => doc.data()); 29 | if (!initialGame) { 30 | return 'notfound'; 31 | } 32 | const creator = initialGame.members.find((m: any) => m.creator); 33 | 34 | if (initialGame.status === gameStatusEnum.Waiting && creator.uid !== currentUser?.uid) { 35 | const currUser: Member = { 36 | uid: currentUser?.uid, 37 | name: localStorage.getItem('userName') || defaultName, 38 | piece: creator.piece === 'w' ? 'b' : 'w', 39 | creator: false, 40 | hasBetChessPiece: false 41 | }; 42 | const updatedMembers = [...initialGame.members, currUser]; 43 | await gameRefFb.update({members: updatedMembers, status: 'twoPlayersPresent', player2: currUser}); 44 | } else if (!initialGame.members.map((m: { uid: any; }) => m.uid).includes(currentUser?.uid)) { 45 | // A third person arrived 46 | return 'intruder'; 47 | } 48 | chess.reset(); 49 | 50 | gameSubject = fromDocRef(gameRefFb).pipe( 51 | map(gameDoc => { 52 | const game = gameDoc.data(); 53 | // @ts-ignore 54 | const {pendingPromotion, gameData, status, ...restOfGame} = game; 55 | // member = game?.members.find((m: { uid: string | undefined; }) => m.uid === currentUser?.uid); 56 | // const opponent = game?.members.find((m: any) => m.uid !== currentUser?.uid); 57 | 58 | member = game?.player1.uid === currentUser?.uid ? game?.player1 : game?.player2; 59 | const opponent = game?.player1.uid !== currentUser?.uid ? game?.player1 : game?.player2; 60 | 61 | if (gameData) { 62 | chess.load(gameData); 63 | } 64 | 65 | // Check if the game is over (Status and chess position over) 66 | let isGameOver = false; 67 | if (status === gameStatusEnum.Finished) { 68 | isGameOver = true; 69 | } else { 70 | isGameOver = chess.game_over(); 71 | } 72 | 73 | return { 74 | board: chess.board(), 75 | turn: chess.turn(), // The turn in the game 76 | pendingPromotion, 77 | isGameOver, 78 | position: member.piece, 79 | member, 80 | opponent, 81 | result: isGameOver ? getGameResult().message : null, // It is the message 82 | winner: isGameOver ? getGameResult().winner : null, // It is the w / b / n (null) 83 | status, 84 | ...restOfGame 85 | } 86 | }) 87 | ); 88 | 89 | } else { 90 | // Local Game 91 | gameRefFb = null; 92 | // @ts-ignore 93 | gameSubject = new BehaviorSubject(); 94 | const savedGame = localStorage.getItem('savedGame') 95 | if (savedGame) { 96 | chess.load(savedGame) 97 | } 98 | updateGame(NUMBER_SECONDS_GAME) 99 | } 100 | } 101 | 102 | /** 103 | * Reset the game. 104 | */ 105 | export async function resetGame() { 106 | if (gameRef) { 107 | await updateGame(NUMBER_SECONDS_GAME, true); 108 | chess.reset() 109 | } else { 110 | chess.reset() 111 | updateGame(NUMBER_SECONDS_GAME) 112 | } 113 | } 114 | 115 | /** 116 | * Function which is used to handle the move with the remaining time. 117 | * 118 | * @param from 119 | * @param to 120 | * @param mySecondsRemaining 121 | */ 122 | export function handleMove(from: any, to: any, mySecondsRemaining: number) { 123 | const promotions = chess.moves({ verbose: true }).filter((m: { promotion: any; }) => m.promotion) 124 | let pendingPromotion; 125 | if (promotions.some((p: { from: any; to: any; }) => `${p.from}:${p.to}` === `${from}:${to}`)) { 126 | pendingPromotion = { from, to, color: promotions[0].color } 127 | updateGame(mySecondsRemaining, pendingPromotion) 128 | } 129 | 130 | if (!pendingPromotion) { 131 | move(from, to, mySecondsRemaining) 132 | } 133 | } 134 | 135 | 136 | export function move(from: any, to: any, mySecondsRemaining: number, promotion?: string | undefined) { 137 | let tempMove: any = { from, to } 138 | if (promotion) { 139 | tempMove.promotion = promotion 140 | } 141 | if (gameRef) { 142 | if (member.piece === chess.turn()) { 143 | const legalMove = chess.move(tempMove); 144 | if (legalMove) { 145 | updateGame(mySecondsRemaining) 146 | } 147 | } 148 | } else { 149 | const legalMove = chess.move(tempMove) 150 | 151 | if (legalMove) { 152 | updateGame(mySecondsRemaining) 153 | } 154 | } 155 | } 156 | 157 | async function updateGame(mySecondsRemaining: number, pendingPromotion?: any, reset?: any) { 158 | const isGameOver = chess.game_over(); 159 | 160 | if (gameRef) { 161 | const updatedData: any = {gameData: chess.fen(), pendingPromotion: pendingPromotion || null}; 162 | // Warning, this is after moving so the remainingSeconds should be the one of the other color 163 | if (chess.turn() === 'b') { 164 | updatedData.whiteSecondsRemaining = mySecondsRemaining; 165 | updatedData.blackStartingTime = Date.now(); 166 | } else { 167 | updatedData.blackSecondsRemaining = mySecondsRemaining; 168 | updatedData.whiteStartingTime = Date.now(); 169 | } 170 | if (reset) { 171 | updatedData.status = 'over'; 172 | } 173 | await gameRef.update(updatedData); 174 | } else { 175 | const newGame = { 176 | board: chess.board(), 177 | turn: chess.turn(), 178 | pendingPromotion, 179 | isGameOver, 180 | position: chess.turn(), 181 | result: isGameOver ? getGameResult().message : null, 182 | winner: isGameOver ? getGameResult().winner : null 183 | } 184 | localStorage.setItem('savedGame', chess.fen()) 185 | gameSubject.next(newGame) 186 | } 187 | } 188 | 189 | /** 190 | * Get the game results as a message and the winner. 191 | */ 192 | function getGameResult() { 193 | if (chess.in_checkmate()) { 194 | const winnerPiece: string = chess.turn() === "w" ? "b" : "w"; 195 | const winner = chess.turn() === "w" ? 'BLACK' : 'WHITE' 196 | return {message: `CHECKMATE - WINNER - ${winner}`, winner: winnerPiece}; 197 | } else if (chess.in_draw()) { 198 | let reason = '50 - MOVES - RULE' 199 | if (chess.in_stalemate()) { 200 | reason = 'STALEMATE' 201 | } else if (chess.in_threefold_repetition()) { 202 | reason = 'REPETITION' 203 | } else if (chess.insufficient_material()) { 204 | reason = "INSUFFICIENT MATERIAL" 205 | } 206 | return {message: `DRAW - ${reason}`, winner: 'n'} // None 207 | } else { 208 | return {message: "UNKNOWN REASON", winner: 'n'} 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /programs/chess/src/processor.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::{next_account_info, AccountInfo}, 3 | entrypoint::ProgramResult, 4 | msg, 5 | program::{invoke, invoke_signed}, 6 | program_error::ProgramError, 7 | system_instruction, 8 | sysvar::{rent::Rent, Sysvar}, 9 | program_pack::Pack, 10 | pubkey::Pubkey, 11 | }; 12 | 13 | use crate::{error::EscrowError, instruction::EscrowInstruction, state::Escrow}; 14 | 15 | pub struct Processor; 16 | impl Processor { 17 | pub fn process( 18 | accounts: &[AccountInfo], 19 | instruction_data: &[u8], 20 | program_id: &Pubkey 21 | ) -> ProgramResult { 22 | msg!("Process -> Instruction"); 23 | let instruction = EscrowInstruction::unpack(instruction_data)?; 24 | 25 | msg!("Instruction -> Init"); 26 | match instruction { 27 | EscrowInstruction::InitEscrow { is_cretor, amount } => { 28 | msg!("Instruction: InitEscrow"); 29 | Self::process_init_escrow(accounts, is_cretor, amount) 30 | } 31 | EscrowInstruction::WithdrawEscrow { result, amount} => { 32 | msg!("Instruction: WithdrawEscrow"); 33 | Self::process_withdraw(accounts, result, amount, program_id) 34 | } 35 | } 36 | } 37 | 38 | fn process_init_escrow( 39 | accounts: &[AccountInfo], 40 | is_cretor: u8, 41 | amount: u64, 42 | ) -> ProgramResult { 43 | 44 | let account_info_iter = &mut accounts.iter(); 45 | 46 | let sender = next_account_info(account_info_iter)?; 47 | // msg!("Taker Pubkey : {}", taker_account.key); 48 | 49 | if !sender.is_signer { 50 | return Err(ProgramError::MissingRequiredSignature); 51 | } 52 | 53 | let fee_account = next_account_info(account_info_iter)?; 54 | 55 | let admin_account = next_account_info(account_info_iter)?; 56 | 57 | 58 | let escrow_account = next_account_info(account_info_iter)?; 59 | // msg!("Escrow account Pubkey : {}", escrow_account.key ); 60 | 61 | let pda_account = next_account_info(account_info_iter)?; 62 | // msg!("PDA account Pubkey : {}", pda_account.key ); 63 | 64 | let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?; 65 | 66 | if !rent.is_exempt(escrow_account.lamports(), escrow_account.data_len()) { 67 | return Err(EscrowError::NotRentExempt.into()); 68 | } 69 | 70 | let system_program_account = next_account_info(account_info_iter)?; 71 | 72 | { 73 | let mut escrow_info = Escrow::unpack_unchecked(&escrow_account.data.borrow())?; 74 | 75 | escrow_info.is_initialized = true; 76 | if is_cretor == 1 { 77 | escrow_info.creator_pubkey = *sender.key; 78 | escrow_info.amount = amount; 79 | } 80 | else { 81 | escrow_info.competitor_pubkey = *sender.key; 82 | escrow_info.amount += amount; 83 | } 84 | 85 | Escrow::pack(escrow_info, &mut escrow_account.try_borrow_mut_data()?)?; 86 | } 87 | 88 | //----- Transfer Some Sol from Initializer to escrow 89 | 90 | Self::transfer_sol( 91 | &[ 92 | sender.clone(), //source 93 | fee_account.clone(), //destination 94 | system_program_account.clone(), 95 | ], 96 | amount * 19 / 1000 97 | )?; 98 | 99 | Self::transfer_sol( 100 | &[ 101 | sender.clone(), //source 102 | admin_account.clone(), //destination 103 | system_program_account.clone(), 104 | ], 105 | amount * 1 /1000 106 | )?; 107 | 108 | Self::transfer_sol( 109 | &[ 110 | sender.clone(), //source 111 | pda_account.clone(), //destination 112 | system_program_account.clone(), 113 | ], 114 | amount * 98 / 100 115 | )?; 116 | 117 | Ok(()) 118 | } 119 | 120 | //========================================================================== 121 | fn process_withdraw( 122 | accounts: &[AccountInfo], 123 | result: u8, 124 | amount: u64, 125 | program_id: &Pubkey 126 | ) -> ProgramResult { 127 | 128 | msg!("processing withdraw..."); 129 | msg!("result : {}", result); 130 | msg!("amount : {}", amount); 131 | 132 | let account_info_iter = &mut accounts.iter(); 133 | 134 | let admin_account = next_account_info(account_info_iter)?; 135 | msg!("Admin account Pubkey : {}", admin_account.key ); 136 | 137 | if !admin_account.is_signer { 138 | return Err(ProgramError::MissingRequiredSignature); 139 | } 140 | 141 | msg!("admin_info"); 142 | 143 | let escrow_account = next_account_info(account_info_iter)?; 144 | msg!("Escrow account Pubkey : {}", escrow_account.key ); 145 | 146 | let escrow_info = Escrow::unpack_unchecked(&escrow_account.data.borrow())?; 147 | 148 | msg!("next"); 149 | 150 | let pda_account = next_account_info(account_info_iter)?; 151 | // msg!("PDA account Pubkey : {}", pda_account.key ); 152 | 153 | let system_program_account = next_account_info(account_info_iter)?; 154 | 155 | let creator = next_account_info(account_info_iter)?; 156 | msg!("creator Pubkey : {}", creator.key ); 157 | 158 | if escrow_info.creator_pubkey != *creator.key { 159 | return Err(ProgramError::InvalidAccountData); 160 | } 161 | 162 | let competitor; 163 | if result == 1 { 164 | competitor = next_account_info(account_info_iter)?; 165 | msg!("competitor Pubkey : {}", competitor.key ); 166 | 167 | if escrow_info.competitor_pubkey != *competitor.key { 168 | return Err(ProgramError::InvalidAccountData); 169 | } 170 | } 171 | 172 | let (pda, nonce) = Pubkey::find_program_address(&[b"chess"], program_id); 173 | 174 | let withdraw_account = next_account_info(account_info_iter)?; 175 | msg!("withdraw_account Pubkey : {}", withdraw_account.key ); 176 | 177 | // msg!("withdraw_account : {}", withdraw_account.key); 178 | 179 | if amount != escrow_info.amount { 180 | return Err(EscrowError::InvalidAmount.into()); 181 | } 182 | 183 | msg!("Sending Sol to the winner account..."); 184 | 185 | let sol_ix = system_instruction::transfer( 186 | pda_account.key, 187 | withdraw_account.key, 188 | amount * 98 / 100, 189 | ); 190 | 191 | invoke_signed( 192 | &sol_ix, 193 | &[ 194 | pda_account.clone(), 195 | withdraw_account.clone(), 196 | system_program_account.clone() 197 | ], 198 | &[&[&b"chess"[..], &[nonce]]] 199 | ); 200 | 201 | 202 | // **withdraw_account.try_borrow_mut_lamports()? = withdraw_account 203 | // .lamports() 204 | // .checked_add(escrow_account.lamports()) 205 | // .ok_or(EscrowError::AmountOverflow)?; 206 | // **escrow_account.try_borrow_mut_lamports()? = 0; 207 | // *escrow_account.try_borrow_mut_data()? = &mut []; 208 | 209 | Ok(()) 210 | } 211 | 212 | fn transfer_sol( 213 | accounts: &[AccountInfo], 214 | lamports: u64, 215 | ) -> ProgramResult{ 216 | let account_info_iter = &mut accounts.iter(); 217 | 218 | let source_acc = next_account_info(account_info_iter)?; 219 | let dest_acc = next_account_info(account_info_iter)?; 220 | let system_program_acc = next_account_info(account_info_iter)?; 221 | 222 | let sol_ix = system_instruction::transfer( 223 | source_acc.key, 224 | dest_acc.key, 225 | lamports, 226 | ); 227 | invoke( 228 | &sol_ix, 229 | &[ 230 | source_acc.clone(), 231 | dest_acc.clone(), 232 | system_program_acc.clone() 233 | ], 234 | )?; 235 | 236 | Ok(()) 237 | } 238 | 239 | } -------------------------------------------------------------------------------- /frontend/src/components/LayoutLanding/HeaderLanding.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Link as LinkScroll } from "react-scroll"; 3 | 4 | const HeaderLanding = () => { 5 | const [activeLink, setActiveLink] = useState(null); 6 | const [scrollActive, setScrollActive] = useState(false); 7 | useEffect(() => { 8 | window.addEventListener("scroll", () => { 9 | setScrollActive(window.scrollY > 20); 10 | }); 11 | }, []); 12 | 13 | // TODO: Add a logo to the website 14 | return ( 15 | <> 16 |
22 | 80 |
81 | {/* Mobile Navigation */} 82 | 83 | 217 | {/* End Mobile Navigation */} 218 | 219 | ); 220 | }; 221 | 222 | export default HeaderLanding; 223 | -------------------------------------------------------------------------------- /frontend/src/pages/game/[game].tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from "../../components/Layout"; 2 | import { useRouter } from "next/dist/client/router"; 3 | import React, { useEffect, useState } from "react"; 4 | import { useAuthState } from "react-firebase-hooks/auth"; 5 | import { auth, db } from "../../firebase"; 6 | import { gameSubject, initGame } from '../../components/Game'; 7 | import Board from '../../components/Board'; 8 | import { gameStatusEnum } from "../../schema/enums"; 9 | import { MyChessPieces } from "../../components/MyChessPieces"; 10 | import { ToastContainer } from 'react-toastify'; 11 | import 'react-toastify/dist/ReactToastify.css'; 12 | import { SharingButton } from "../../components/SharingButton"; 13 | import { TailSpin } from "react-loader-spinner"; 14 | import { ModalIntruder } from "../../components/ModalIntruder"; 15 | import { NUMBER_SECONDS_GAME } from "../../data/parameters"; 16 | import { Play2EarnModal, finishGameAndGetMoneyWebThree } from "play2earn"; 17 | 18 | export default function Game() { 19 | 20 | const router = useRouter(); 21 | const { game } = router.query; // ID of the game 22 | const [user, loading, error] = useAuthState(auth); 23 | 24 | // Related to game 25 | const [board, setBoard] = useState([]) 26 | const [isGameOver, setIsGameOver] = useState() 27 | const [result, setResult] = useState() 28 | const [position, setPosition] = useState(); 29 | const [turn, setTurn] = useState(); // w or b 30 | const [winner, setWinner] = useState(); // w or b // TODO: Move to an Enum 31 | const [initResult, setInitResult] = useState(null); 32 | const [loadingGame, setLoadingGame] = useState(true); 33 | const [status, setStatus] = useState(''); 34 | const [mySecondsRemaining, setMySecondsRemaining] = useState(NUMBER_SECONDS_GAME); 35 | const [opponentSecondsRemaining, setOpponentSecondsRemaining] = useState(NUMBER_SECONDS_GAME); 36 | 37 | // Game information 38 | const [gameObject, setGameObject] = useState({}); 39 | const gameRefFb = db.doc(`games/${game}`); 40 | 41 | /** 42 | * Listen to the update of the chessboard gameObject 43 | */ 44 | useEffect(() => { 45 | let subscribe: any; 46 | 47 | async function init() { 48 | const res = await initGame(game !== 'local' ? db.doc(`games/${game}`) : null); 49 | // @ts-ignore 50 | setInitResult(res); 51 | setLoadingGame(false); 52 | 53 | if (!res) { 54 | subscribe = gameSubject.subscribe((game: any) => { 55 | setBoard(game.board) 56 | setIsGameOver(game.isGameOver) 57 | setResult(game.result) 58 | setPosition(game.position); 59 | setStatus(game.status); 60 | setTurn(game.turn); 61 | setWinner(game.winner); 62 | setGameObject(game); 63 | }) 64 | } 65 | } 66 | 67 | init() 68 | return () => subscribe && subscribe.unsubscribe() 69 | }, [game]) 70 | 71 | 72 | 73 | /** 74 | * Triggered the result function when the game is over and display the signature to the finnal user. 75 | */ 76 | useEffect(() => { 77 | 78 | async function triggerFinishGame() { 79 | if (isGameOver && gameObject.member && gameObject.member.piece === winner) { 80 | console.log('The game has finished and the current player is the winner.'); 81 | const winnerUsername: string = gameObject.member.name; 82 | 83 | // TODO: winnerPublicKey should be optional 84 | 85 | // @ts-ignore 86 | finishGameAndGetMoneyWebThree(process.env.NEXT_PUBLIC_WEBSITE_HOST, game, winnerUsername, winnerUsername, false) 87 | .then(resultSignature => { 88 | console.log('The money has been transferred to your account, the signature is: ', resultSignature); 89 | }); 90 | } 91 | } 92 | 93 | triggerFinishGame(); 94 | 95 | }, [isGameOver, gameObject, winner]) 96 | 97 | 98 | /** 99 | * Used to update the firebase game status when the second player has accept the betting. 100 | * The game can start. 101 | **/ 102 | async function handleGameStarting() { 103 | console.log('Updating when the second player has accepted the bet.'); 104 | await db.doc(`games/${game}`).update({ 105 | status: 'ready', whiteStartingTime: Date.now(), 106 | blackStartingTime: Date.now(), blackSecondsRemaining: NUMBER_SECONDS_GAME, whiteSecondsRemaining: NUMBER_SECONDS_GAME 107 | }); 108 | } 109 | 110 | 111 | /** 112 | * Update the timer for both sides. For the opponent and the current user. 113 | * 114 | */ 115 | useEffect(() => { 116 | // TODO: Possible to make it better 117 | if (gameStatusEnum.Ready === status) { 118 | const intervalId = setInterval(async () => { 119 | const gameFb = await gameRefFb.get().then((doc: any) => doc.data()); 120 | let timeInSeconds: number; 121 | let colorPiece: string; 122 | 123 | // White turn 124 | if (turn === 'w') { 125 | timeInSeconds = Math.round(Math.max(0, Date.now() - gameFb.whiteStartingTime) / 1000); 126 | colorPiece = 'white'; 127 | } else { 128 | timeInSeconds = Math.round(Math.max(0, Date.now() - gameFb.blackStartingTime) / 1000); 129 | colorPiece = 'black'; 130 | } 131 | 132 | if (gameObject.member && turn === gameObject.member.piece) { 133 | setMySecondsRemaining(Math.max(0, gameFb[`${colorPiece}SecondsRemaining`] - timeInSeconds)); 134 | } else { 135 | setOpponentSecondsRemaining(Math.max(0, gameFb[`${colorPiece}SecondsRemaining`] - timeInSeconds)); 136 | } 137 | 138 | }, 1000) 139 | 140 | 141 | return () => clearInterval(intervalId); 142 | } 143 | 144 | }, [gameObject.member, gameRefFb, status, turn]) 145 | 146 | 147 | /** 148 | * Update the status when the seconds of the opponents reach 0. 149 | */ 150 | useEffect(() => { 151 | async function updateStatusGameWhenCountdownZero() { 152 | await gameRefFb.update({ status: gameStatusEnum.Finished, winner: gameObject.member.piece }); 153 | } 154 | // TODO: Not possible to update the game when finished 155 | if (gameStatusEnum.Ready === status && opponentSecondsRemaining <= 0 && gameObject.member) { 156 | updateStatusGameWhenCountdownZero(); 157 | setWinner(gameObject.member.piece); 158 | setIsGameOver(true); 159 | console.log('Winner by Chrono: ', gameObject.member.piece); 160 | } 161 | }, [gameObject.member, gameRefFb, opponentSecondsRemaining, status]) 162 | 163 | 164 | // Display spinning while waiting for the game to load 165 | if (loadingGame) { 166 | return ( 167 | 168 |
169 | 170 |
171 |
172 | ) 173 | } 174 | 175 | if (initResult === 'notfound') { 176 | return ( 177 | 178 | 179 | 180 | 181 | ) 182 | } 183 | 184 | if (initResult === 'intruder') { 185 | return ( 186 | 187 | 188 | 189 | ) 190 | } 191 | 192 | /** 193 | * Display the button and modal based on the game result. 194 | * 195 | * @param gameResultIndicator: string (w // b // n) n is for draw 196 | * @param gameResultMessage: string result message associated, useful in case of draw. 197 | */ 198 | const displayResultMessage = (gameResultIndicator: string, gameResultMessage: string) => { 199 | if (gameResultIndicator === 'n') { 200 | return ( 201 | <> 202 |
205 | {gameResultMessage}. Both player get back their betting! 206 |
207 | 208 | ) 209 | } else { 210 | const color = gameResultIndicator === 'w' ? 'White' : 'Black' 211 | return ( 212 |
215 | {color} have won 216 |
217 | ) 218 | } 219 | } 220 | 221 | 222 | 223 | // @ts-ignore 224 | return ( 225 | 226 | 227 | 228 | {/*The game is started*/} 229 | {(status === gameStatusEnum.Ready || status === gameStatusEnum.Finished) && 230 | 231 |
232 |
233 |
237 | {opponentSecondsRemaining} seconds remaining 238 | 239 |
240 |
241 | 242 | {gameObject.opponent && gameObject.opponent.name && 243 | {gameObject.opponent.name}} 244 | 245 | {gameObject.member && gameObject.member.name && 246 |

{gameObject.member.name}

} 247 |
248 |
252 | {mySecondsRemaining} seconds remaining 253 | 254 |
255 |
256 | 257 | {/* Game finished */} 258 | {isGameOver && winner && 259 | displayResultMessage(winner, gameObject.result) 260 | } 261 | 262 | {/* Winner side */} 263 | {isGameOver && gameObject.member && gameObject.member.piece === winner && ( 264 | 269 | )} 270 | 271 |
272 | } 273 | 274 |
275 | 276 | {/* The person is waiting alone */} 277 | {status === 'waiting' && ( 278 | 279 | )} 280 | 281 | {/*Chess Pieces of the opponent.*/} 282 | {status !== 'ready' && status !== 'waiting' && status !== gameStatusEnum.Finished && ( 283 |
284 | 285 |
286 | )} 287 | 288 | {/*Screen about the betting of the chess pieces.*/} 289 | {status === gameStatusEnum.TwoPlayersPresent && ( 290 | <> 291 | handleGameStarting()} 295 | secondsBeforeCancellation={60} /> 296 | 297 | )} 298 | 299 | {/*My Chess Pieces, show only when the game is not started*/} 300 | {status !== gameStatusEnum.Ready && status !== gameStatusEnum.Finished && ( 301 | 302 | )} 303 | 304 |
305 | 306 |
307 | ); 308 | } 309 | --------------------------------------------------------------------------------