├── 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 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/checklist.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/stars.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/frontend/public/Icon/jam_check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/heroicons_sm-user.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/public/Icon/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/jam_check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/public/Icon/gridicons_location.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/gridicons_location.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/Icon/bx_bxs-server.svg:
--------------------------------------------------------------------------------
1 |
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 |
27 |
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 |
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 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/frontend/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
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 |
--------------------------------------------------------------------------------