first commit

This commit is contained in:
unknown
2023-01-27 20:50:01 +08:00
commit a5e17d8c5d
69 changed files with 12547 additions and 0 deletions

101
.eslintrc.js Normal file
View File

@ -0,0 +1,101 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:unicorn/recommended',
'plugin:sonarjs/recommended',
'plugin:react/jsx-runtime',
'next/core-web-vitals',
],
overrides: [
{
files: ['**/*.jsx'],
rules: {
'unicorn/filename-case': [
'error',
{
case: 'pascalCase',
},
],
},
},
{
files: ['pages/**/*'],
rules: {
'import/no-default-export': 'off',
'unicorn/filename-case': 'off',
},
},
],
parserOptions: {
ecmaVersion: '2022',
},
plugins: ['react', 'chakra-ui', 'sort-keys-fix', 'prettier'],
rules: {
'chakra-ui/props-order': 'error',
'chakra-ui/props-shorthand': 'error',
'chakra-ui/require-specific-component': 'error',
'import/no-default-export': 'error',
'import/order': 'error',
'no-console': 'warn',
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
'react/boolean-prop-naming': 'error',
'react/destructuring-assignment': ['error', 'never'],
'react/function-component-definition': [
'error',
{
namedComponents: 'function-declaration',
unnamedComponents: 'arrow-function',
},
],
'react/hook-use-state': 'error',
'react/jsx-boolean-value': 'error',
'react/jsx-curly-brace-presence': [
'error',
{ children: 'never', propElementValues: 'always', props: 'never' },
],
'react/jsx-fragments': 'error',
'react/jsx-handler-names': 'error',
'react/jsx-no-useless-fragment': 'error',
'react/jsx-pascal-case': 'error',
'react/jsx-wrap-multilines': 'error',
'react/no-array-index-key': 'error',
'react/no-invalid-html-attribute': 'error',
'react/no-this-in-sfc': 'error',
'react/no-typos': 'error',
'react/no-unstable-nested-components': 'error',
'react/no-unused-prop-types': 'error',
'react/prefer-stateless-function': 'error',
'sort-keys-fix/sort-keys-fix': 'warn',
'unicorn/filename-case': [
'error',
{
case: 'kebabCase',
},
],
'unicorn/no-null': 'off',
'unicorn/no-unsafe-regex': 'error',
'unicorn/prefer-at': [
'error',
{
checkAllIndexAccess: true,
},
],
'unicorn/prefer-module': 'off',
'unicorn/prevent-abbreviations': 'off',
},
settings: {
'import/resolver': {
alias: {
extensions: ['.js', '.jsx'],
map: [['@draughts', './packages/draughts']],
},
},
},
};

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v18.1.0

1
New Text Document.bat Normal file
View File

@ -0,0 +1 @@
start "" http://localhost:3000 && npm run dev

View File

@ -0,0 +1,3 @@
export function BottomBannerAdvert() {
return <div id="ezoic-pub-ad-placeholder-130" />;
}

View File

@ -0,0 +1,3 @@
export function LeftSidebarAdvert() {
return <div id="ezoic-pub-ad-placeholder-133" />;
}

View File

@ -0,0 +1,3 @@
export function RightSidebarAdvert() {
return <div id="ezoic-pub-ad-placeholder-132" />;
}

View File

@ -0,0 +1,3 @@
export function TopBannerAdvert() {
return <div id="ezoic-pub-ad-placeholder-124" />;
}

View File

@ -0,0 +1,61 @@
import { Text, VStack } from '@chakra-ui/react';
export function DraughtsRulesContent() {
return (
<VStack align="start" spacing={2}>
<Text>
Permainan draf dimainkan di papan catur 64 persegi dengan delapan
deretan kotak berwarna gelap dan terang bergantian.
</Text>
<Text>
Ada dua pemain dan masing-masing memulai permainan dengan masing-masing 12 draf
pemain memiliki warna mereka sendiri.
</Text>
<Text>
Para pemain menempatkan draf mereka di tiga baris kotak gelap yang
paling dekat dengan mereka.
</Text>
<Text>Para pemain kemudian mulai bermain, melakukan satu gerakan pada satu waktu.</Text>
<Text>
Tujuan permainan ini adalah membuat lawan tidak bisa bergerak
ketika tiba giliran mereka.
</Text>
<Text>
Ini dilakukan dengan mengambil semua bidak mereka sepanjang permainan, atau
memblokir mereka sehingga mereka tidak punya tempat untuk bergerak.
</Text>
<Text>
Draf tunggal hanya dapat bergerak dalam arah diagonal ke depan ke a
persegi tanpa bagian lain di dalamnya.
</Text>
<Text>
Jika bidak lawan ada di kotak berikutnya, pemain bisa melompat
di atasnya dan tangkap, lepaskan potongan itu dari papan. Mereka hanya bisa
lakukan ini jika kotak berikutnya kosong.
</Text>
<Text>Pemain tidak pernah bisa melompati bagian mereka sendiri.</Text>
<Text>
Ketika seorang pemain berjalan melintasi papan ke yang lain
sisi pemain, bidak mereka akan berubah menjadi Raja. Ketika ini terjadi,
salah satu bidak yang diambil sebelumnya akan diletakkan di atas bidak
yang membuatnya ke sisi itu.
</Text>
<Text>
Setelah sepotong dibuat menjadi raja, itu akan dapat bergerak maju dan
mundur, memberikan lebih banyak kesempatan untuk menangkap bagian lawan.
</Text>
<Text>
Seorang raja dapat melompat sebanyak mungkin sehubungan dengan
kotak yang diperlukan sedang kosong. Namun, raja tidak bisa melompati
potongan-potongan yang memiliki warna yang sama dengan mereka.
</Text>
<Text>
Permainan akan berakhir setelah pemain tidak bisa lagi bergerak.
</Text>
<Text>
Jika kedua pemain tidak bisa bergerak kemana-mana, permainan akan berakhir dengan a
mengikat, atau menggambar.
</Text>
</VStack>
);
}

View File

@ -0,0 +1,26 @@
import PropTypes from 'prop-types';
import {
DraughtsSettingsProvider,
DraughtsSettingsProviderProps,
} from './settings/DraughtsSettingsContext';
import {
DraughtsBoardProvider,
DraughtsBoardProviderProps,
} from './board/DraughtsBoardContext';
import { DraughtsGameProvider } from './game/DraughtsGameContext';
export function DraughtsProvider(props) {
return (
<DraughtsSettingsProvider {...props.settings}>
<DraughtsBoardProvider {...props.board}>
<DraughtsGameProvider>{props.children}</DraughtsGameProvider>
</DraughtsBoardProvider>
</DraughtsSettingsProvider>
);
}
DraughtsProvider.propTypes = {
board: PropTypes.shape(DraughtsBoardProviderProps),
children: PropTypes.node.isRequired,
settings: PropTypes.shape(DraughtsSettingsProviderProps),
};

View File

@ -0,0 +1,59 @@
import { HStack, VStack, Text, keyframes } from '@chakra-ui/react';
import { useDraughtsPlayerToMove } from './board/hooks/use-draughts-player-to-move';
import { useDraughtsGame } from './game/DraughtsGameContext';
import { useDraughtsSettings } from './settings/DraughtsSettingsContext';
import { Players } from '@draughts/core';
const glowKeyframe = keyframes`
from {
background-color: rgba(144, 238, 144, 0.3)
}
to {
background-color: rgba(144, 238, 144, 0.6)
}
`;
const glowAnimation = `${glowKeyframe} 1s ease 0s infinite alternate`;
function formatTimer({ seconds, minutes }) {
const formatSeconds = String(seconds).padStart(2, '0');
return `${minutes}:${formatSeconds}`;
}
export function DraughtsGameInfoView() {
const { userPlayer } = useDraughtsSettings();
const { whiteTimer, blackTimer } = useDraughtsGame();
const whiteToMove = useDraughtsPlayerToMove(Players.WHITE);
const blackToMove = useDraughtsPlayerToMove(Players.BLACK);
return (
<VStack justify="space-between">
<HStack
align="center"
justify="space-between"
gap={3}
px={2}
borderRadius="0.2em"
animation={whiteToMove ? glowAnimation : undefined}
>
<Text fontSize="sm">
putih {userPlayer === Players.WHITE ? '(Anda)' : '(AI)'}
</Text>
<time>{formatTimer(whiteTimer)}</time>
</HStack>
<HStack
align="center"
justify="space-between"
gap={3}
px={2}
borderRadius="0.2em"
animation={blackToMove ? glowAnimation : undefined}
>
<Text fontSize="sm">
hitam {userPlayer === Players.BLACK ? '(Anda)' : '(AI)'}
</Text>
<time>{formatTimer(blackTimer)}</time>
</HStack>
</VStack>
);
}

View File

@ -0,0 +1,51 @@
import { createContext, useCallback, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { Board, Pieces, Players } from '@draughts/core';
export const DraughtsBoardContext = createContext();
export const useDraughtsBoard = () => useContext(DraughtsBoardContext);
export function DraughtsBoardProvider(props) {
const [lastMove, setLastMove] = useState(null);
const [board, setBoard] = useState(
new Board(props.position, props.playerToMove)
);
const doMove = useCallback((move) => {
setBoard((board) => {
return board.doMove(move);
});
setLastMove(move.path.at(-1));
}, []);
const resetBoard = useCallback(() => {
setBoard(new Board(props.position, props.playerToMove));
setLastMove(null);
}, [props.position, props.playerToMove]);
return (
<DraughtsBoardContext.Provider
value={{
board,
doMove,
lastMove,
resetBoard,
}}
>
{props.children}
</DraughtsBoardContext.Provider>
);
}
export const DraughtsBoardProviderProps = {
playerToMove: PropTypes.oneOf(Object.values(Players)),
position: PropTypes.arrayOf(
PropTypes.arrayOf(PropTypes.oneOf(Object.values(Pieces)))
).isRequired,
};
DraughtsBoardProvider.propTypes = {
children: PropTypes.node.isRequired,
...DraughtsBoardProviderProps,
};

View File

@ -0,0 +1,6 @@
import { useDraughtsBoard } from '../DraughtsBoardContext';
export function useDraughtsPlayerToMove(player) {
const { board } = useDraughtsBoard();
return player === board.playerToMove;
}

View File

@ -0,0 +1,17 @@
import { useMemo } from 'react';
import { useDraughtsBoard } from '../DraughtsBoardContext';
import { useDraughtsSettings } from '../../settings/DraughtsSettingsContext';
import { Players, GameStates } from '@draughts/core';
export function useDraughtsWinner() {
const { board } = useDraughtsBoard();
const { userPlayer } = useDraughtsSettings();
const winner = useMemo(() => {
if (board.state === GameStates.WHITE_WON) return Players.WHITE;
if (board.state === GameStates.BLACK_WON) return Players.BLACK;
return Players.NONE;
}, [board.state]);
return { userWon: winner === userPlayer, winner };
}

View File

@ -0,0 +1,65 @@
import { useMemo } from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd';
import { Grid, GridItem } from '@chakra-ui/react';
import { TouchBackend } from 'react-dnd-touch-backend';
import { useDraughtsBoard } from '../DraughtsBoardContext';
import { useDraughtsSettings } from '../../settings/DraughtsSettingsContext';
import { DraughtsCell } from './DraughtsCell';
import { DraughtsGameOverModal } from './DraughtsGameOverModal';
import { Players } from '@draughts/core';
function isTouchDevice() {
return (
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
);
}
export function DraughtsBoard() {
const { board } = useDraughtsBoard();
const { userPlayer } = useDraughtsSettings();
const backend = useMemo(() => {
if (typeof window === 'undefined') return HTML5Backend;
return isTouchDevice() ? TouchBackend : HTML5Backend;
}, []);
const rows = useMemo(() => {
const entries = board.position.map((row, rowIndex) => ({
row,
rowIndex,
}));
if (userPlayer === Players.WHITE) {
return entries;
}
return entries.reverse();
}, [userPlayer, board.position]);
return (
<DndProvider backend={backend}>
<DraughtsGameOverModal />
<Grid
templateRows="repeat(8, 1fr)"
templateColumns="repeat(8, 1fr)"
w="100%"
h="100%"
style={{ aspectRatio: 1 }}
>
{rows.map(({ row, rowIndex }) =>
row.map((piece, colIndex) => (
// eslint-disable-next-line react/no-array-index-key
<GridItem key={`${rowIndex}:${colIndex}:${piece}`}>
<DraughtsCell
piece={piece}
rowIndex={rowIndex}
colIndex={colIndex}
/>
</GridItem>
))
)}
</Grid>
</DndProvider>
);
}

View File

@ -0,0 +1,78 @@
import PropTypes from 'prop-types';
import { useDrop } from 'react-dnd';
import { Box, Square } from '@chakra-ui/react';
import { useDraughtsBoard } from '../DraughtsBoardContext';
import { DraughtsPiece } from './DraughtsPiece';
import { compareCells, Pieces } from '@draughts/core';
DraughtsCell.propTypes = {
colIndex: PropTypes.number.isRequired,
piece: PropTypes.oneOf(Object.values(Pieces)).isRequired,
rowIndex: PropTypes.number.isRequired,
};
export function DraughtsCell(props) {
const { board, doMove, lastMove } = useDraughtsBoard();
const isDark = (props.rowIndex + props.colIndex) % 2 === 0;
const currentCell = { col: props.colIndex, row: props.rowIndex };
const isLastMove = compareCells(currentCell, lastMove);
const validMovePredicate = (start) => (move) => {
const startMove = move.path.at(0);
const endMove = move.path.at(-1);
return compareCells(startMove, start) && compareCells(endMove, currentCell);
};
const [{ isOver, canDrop }, dropReference] = useDrop(
() => ({
accept: 'piece',
canDrop: (start) => board.moves.some(validMovePredicate(start)),
collect: (monitor) => ({
canDrop: !!monitor.canDrop(),
isOver: !!monitor.isOver(),
}),
drop: (start) => {
doMove(board.moves.find(validMovePredicate(start)));
},
}),
[board.moves, doMove]
);
let bg = 'gray.300';
if (canDrop) {
bg = 'lightblue';
} else if ((canDrop && isOver) || isLastMove) {
bg = 'lightgreen';
} else if (isDark) {
bg = 'gray.500';
}
return (
<Square
ref={dropReference}
pos="relative"
h="100%"
p="0.2em"
bg={bg}
userSelect="none"
>
<Box
pos="absolute"
top="0.1rem"
right="0.1rem"
fontSize="0.4rem"
opacity={0.4}
userSelect="none"
>
{props.rowIndex}, {props.colIndex}
</Box>
{props.piece !== Pieces.NONE && (
<DraughtsPiece
piece={props.piece}
rowIndex={props.rowIndex}
colIndex={props.colIndex}
/>
)}
</Square>
);
}

View File

@ -0,0 +1,13 @@
const { Icon } = require('@chakra-ui/react');
export function DraughtsCrown(props) {
return (
<Icon viewBox="0 0 230 200" {...props}>
<path
d="m9,51 72,21 37-64 37,64 72-21-33,99H42
m75-74a15,29 0 1,0 2,0m71,86-11,33H57l-11-33"
fill="gold"
/>
</Icon>
);
}

View File

@ -0,0 +1,82 @@
import { useEffect } from 'react';
import {
Modal,
Button,
Text,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
useDisclosure,
Divider,
} from '@chakra-ui/react';
import { useDraughtsBoard } from '../DraughtsBoardContext';
import { useDraughtsWinner } from '../hooks/use-draughts-winner';
import { useDraughtsGame } from '../../game/DraughtsGameContext';
import { useDraughtsSettings } from '../../settings/DraughtsSettingsContext';
import { formatPlayer, Players, GameStates } from '@draughts/core';
function capitalizeFirstLetter(string) {
return string.at(0).toUpperCase() + string.slice(1);
}
function formatGameOverMessage(winner) {
if (winner === Players.NONE) return 'The game was a draw.';
// const winnerFormatted = capitalizeFirstLetter(formatPlayer(winner));
// return `${winnerFormatted} won the game.`;
}
export function DraughtsGameOverModal() {
const { isOpen, onOpen, onClose } = useDisclosure();
const { restartGame } = useDraughtsGame();
const { board } = useDraughtsBoard();
const { settingsModal } = useDraughtsSettings();
const { userWon, winner } = useDraughtsWinner();
useEffect(() => {
if (board.state === GameStates.PLAYING) return;
onOpen();
return onClose;
}, [board.state, onOpen, onClose]);
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>
{userWon ? 'Wooo! Anda Menang. 🎉' : 'Semoga lain kali lebih beruntung... 💪'}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>{formatGameOverMessage(winner)}</Text>
<Divider marginY={5} />
<Text>
Mengapa bukan kesulitan komputer yang berbeda, Main Lagi Bosku, Anda Masih Kuat, Bertenaga?
</Text>
</ModalBody>
<ModalFooter>
<Button
mr={3}
colorScheme="blue"
onClick={() => {
onClose();
settingsModal.onOpen();
}}
>
Setting Game
</Button>
<Button
onClick={() => {
restartGame();
onClose();
}}
>
Main Lagi
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}

View File

@ -0,0 +1,64 @@
import { useDrag } from 'react-dnd';
import { Center } from '@chakra-ui/react';
import PropTypes from 'prop-types';
import { useDraughtsBoard } from '../DraughtsBoardContext';
import { useDraughtsSettings } from '../../settings/DraughtsSettingsContext';
import { DraughtsCrown } from './DraughtsCrown';
import {
compareCells,
pieceIsPlayer,
pieceIsQueen,
Pieces,
Players,
} from '@draughts/core';
export function DraughtsPiece(props) {
const { board } = useDraughtsBoard();
const { userPlayer } = useDraughtsSettings();
const isWhite = pieceIsPlayer(props.piece, Players.WHITE);
const activePlayer =
pieceIsPlayer(props.piece, userPlayer) &&
pieceIsPlayer(props.piece, board.playerToMove);
const currentCell = { col: props.colIndex, row: props.rowIndex };
const canDrag =
activePlayer &&
board.moves.some((move) => compareCells(move.path.at(0), currentCell));
const [, dragRef] = useDrag(
() => ({
canDrag: () => canDrag,
isDragging: (monitor) => compareCells(monitor.getItem(), currentCell),
item: currentCell,
type: 'piece',
}),
[board, canDrag, currentCell]
);
return (
<Center
ref={dragRef}
zIndex="1"
w="100%"
h="100%"
opacity={canDrag ? 1 : 0.8}
borderWidth="0.2em"
borderStyle="solid"
borderColor={isWhite ? 'gray.400' : 'gray.600'}
borderRadius="50%"
bgColor={isWhite ? 'yellow.50' : 'gray.900'}
>
{pieceIsQueen(props.piece) && (
<DraughtsCrown w="70%" h="70%" opacity="0.6" userSelect="none" />
)}
</Center>
);
}
DraughtsPiece.propTypes = {
colIndex: PropTypes.number,
piece: PropTypes.oneOf(Object.values(Pieces)).isRequired,
rowIndex: PropTypes.number,
};

View File

@ -0,0 +1,37 @@
import { createContext, useCallback, useContext } from 'react';
import PropTypes from 'prop-types';
import { useDraughtsBoard } from '../board/DraughtsBoardContext';
import { useDraughtsTimer } from './timer/use-draughts-timer';
import { useDraughtsComputer } from './computer/use-draughts-computer';
import { Players } from '@draughts/core';
export const DraughtsGameContext = createContext();
export const useDraughtsGame = () => useContext(DraughtsGameContext);
export function DraughtsGameProvider(props) {
const { resetBoard } = useDraughtsBoard();
const [whiteTimer, resetWhiteTimer] = useDraughtsTimer(Players.WHITE);
const [blackTimer, resetBlackTimer] = useDraughtsTimer(Players.BLACK);
useDraughtsComputer();
const restartGame = useCallback(() => {
resetBoard();
resetWhiteTimer();
resetBlackTimer();
}, [resetBoard, resetWhiteTimer, resetBlackTimer]);
return (
<DraughtsGameContext.Provider
value={{ blackTimer, restartGame, whiteTimer }}
>
{props.children}
</DraughtsGameContext.Provider>
);
}
DraughtsGameProvider.propTypes = {
children: PropTypes.node.isRequired,
};

View File

@ -0,0 +1,31 @@
import { useEffect, useRef } from 'react';
import { useDraughtsSettings } from '../../settings/DraughtsSettingsContext';
import { useDraughtsBoard } from '../../board/DraughtsBoardContext';
import { GameStates } from '@draughts/core';
export function useDraughtsComputer() {
const { board, doMove } = useDraughtsBoard();
const { userPlayer, computerDifficulty } = useDraughtsSettings();
const workerRef = useRef();
useEffect(() => {
if (board.state !== GameStates.PLAYING) return;
if (board.playerToMove === userPlayer) return;
workerRef.current = new Worker(new URL('worker.js', import.meta.url));
const handleWorkerMessage = (event) => {
doMove(event.data);
};
workerRef.current.addEventListener('message', handleWorkerMessage);
workerRef.current.postMessage({ board, computerDifficulty });
return () => {
workerRef.current.removeEventListener('message', handleWorkerMessage);
workerRef.current.terminate();
};
}, [board, userPlayer, computerDifficulty, doMove]);
return null;
}

View File

@ -0,0 +1,40 @@
import { ComputerDifficulty } from '../../settings/constants/computer-difficulty';
import { alphaBetaMove } from '@draughts/computer';
import { Board } from '@draughts/core';
const getRandomMove = (board) => {
return board.moves.at(Math.floor(Math.random() * board.moves.length));
};
const getComputerMove = (board, difficulty) => {
if (difficulty === ComputerDifficulty.MEDIUM) {
return alphaBetaMove(board, 3);
}
if (difficulty === ComputerDifficulty.HARD) {
return alphaBetaMove(board, 6);
}
return getRandomMove(board);
};
const getComputerTimeout = (board) => {
return 200 + board.moves.length * Math.floor(Math.random() * 200);
};
addEventListener('message', (event) => {
const startTime = Date.now();
const board = new Board(
event.data.board.position,
event.data.board.playerToMove,
event.data.board.firstMove
);
const move = getComputerMove(board, event.data.computerDifficulty);
const endTime = Date.now();
const elapsedTime = startTime - endTime;
const waitTime = Math.max(0, getComputerTimeout(board) - elapsedTime);
setTimeout(() => {
postMessage(move);
}, waitTime);
});

View File

@ -0,0 +1,43 @@
import { useState, useEffect, useMemo } from 'react';
import { useDraughtsBoard } from '../../board/DraughtsBoardContext';
import { GameStates } from '@draughts/core';
const TIMER_TICK = 100;
const INITIAL_TIME = 5 * 60 * 1000;
export function useDraughtsTimer(player) {
const { board } = useDraughtsBoard();
const [timer, setTimer] = useState(INITIAL_TIME);
useEffect(() => {
if (board.state !== GameStates.PLAYING) return;
if (board.firstMove) return;
if (board.playerToMove !== player) return;
const interval = setInterval(() => {
setTimer((timer) => {
const updatedTime = timer - TIMER_TICK;
return updatedTime >= 0 ? updatedTime : 0;
});
}, TIMER_TICK);
return () => {
clearInterval(interval);
setTimer((timer) => timer + 1000);
};
}, [player, board, setTimer]);
const timerInfo = useMemo(() => {
const totalSeconds = Math.floor(timer / 1000);
return {
complete: timer === 0,
millis: timer % 1000,
minutes: Math.floor(totalSeconds / 60),
seconds: totalSeconds % 60,
};
}, [timer]);
const resetTimer = () => setTimer(INITIAL_TIME);
return [timerInfo, resetTimer];
}

View File

@ -0,0 +1,41 @@
import { createContext, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { useDisclosure } from '@chakra-ui/react';
import { ComputerDifficulty } from './constants/computer-difficulty';
import { Players } from '@draughts/core';
export const DraughtsSettingsContext = createContext();
export const useDraughtsSettings = () => useContext(DraughtsSettingsContext);
export function DraughtsSettingsProvider(props) {
const settingsModal = useDisclosure();
const [userPlayer, setUserPlayer] = useState(props.userPlayer);
const [computerDifficulty, setComputerDifficulty] = useState(
props.computerDifficulty
);
const updateSettings = (newSettings) => {
setUserPlayer(newSettings.userPlayer);
setComputerDifficulty(newSettings.computerDifficulty);
};
return (
<DraughtsSettingsContext.Provider
value={{ computerDifficulty, settingsModal, updateSettings, userPlayer }}
>
{props.children}
</DraughtsSettingsContext.Provider>
);
}
export const DraughtsSettingsProviderProps = {
computerDifficulty: PropTypes.oneOf(Object.values(ComputerDifficulty)),
userPlayer: PropTypes.oneOf(Object.values(Players)),
};
DraughtsSettingsProvider.propTypes = {
children: PropTypes.node.isRequired,
...DraughtsSettingsProviderProps,
};

View File

@ -0,0 +1,5 @@
export const ComputerDifficulty = {
EASY: 'e',
HARD: 'h',
MEDIUM: 'm',
};

View File

@ -0,0 +1,30 @@
import { SettingsIcon, RepeatIcon } from '@chakra-ui/icons';
import { Button, VStack } from '@chakra-ui/react';
import { useDraughtsGame } from '../../game/DraughtsGameContext';
import { useDraughtsSettings } from '../DraughtsSettingsContext';
import { DraughtsSettingsModal } from './DraughtsSettingsModal';
export function DraughtsMenuView() {
const { restartGame } = useDraughtsGame();
const { settingsModal } = useDraughtsSettings();
return (
<VStack>
<DraughtsSettingsModal />
<Button
onClick={() => settingsModal.onOpen()}
rightIcon={<SettingsIcon />}
size="xs"
>
Buka Setting
</Button>
<Button
onClick={() => restartGame()}
rightIcon={<RepeatIcon />}
size="xs"
>
Mulai Lagi
</Button>
</VStack>
);
}

View File

@ -0,0 +1,94 @@
import { useState } from 'react';
import {
Modal,
Button,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
FormControl,
FormLabel,
Radio,
RadioGroup,
HStack,
Select,
Divider,
} from '@chakra-ui/react';
import { useDraughtsGame } from '../../game/DraughtsGameContext';
import { useDraughtsSettings } from '../DraughtsSettingsContext';
import { ComputerDifficulty } from '../constants/computer-difficulty';
import { Players } from '@draughts/core';
export function DraughtsSettingsModal() {
const { restartGame } = useDraughtsGame();
const {
userPlayer,
updateSettings,
computerDifficulty,
settingsModal: { isOpen, onClose },
} = useDraughtsSettings();
const [computerDifficultySelection, setComputerDifficultySelection] =
useState(computerDifficulty);
const [userPlayerSelection, setUserPlayerSelection] = useState(userPlayer);
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Settings</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl as="fieldset">
<FormLabel as="legend">Pilih Warna</FormLabel>
<RadioGroup
onChange={setUserPlayerSelection}
value={userPlayerSelection}
>
<HStack>
<Radio value={`${Players.WHITE}`}>Putih</Radio>
<Radio value={`${Players.BLACK}`}>Hitam</Radio>
</HStack>
</RadioGroup>
</FormControl>
<Divider marginY={5} />
<FormControl>
<FormLabel htmlFor="computerDifficulty">
Computer difficulty
</FormLabel>
<Select
id="computerDifficulty"
onChange={(event) => {
setComputerDifficultySelection(event.target.value);
}}
value={computerDifficultySelection}
>
<option value={ComputerDifficulty.EASY}>Easy</option>
<option value={ComputerDifficulty.MEDIUM}>Medium</option>
<option value={ComputerDifficulty.HARD}>Hard</option>
</Select>
</FormControl>
</ModalBody>
<ModalFooter>
<Button mr={3} colorScheme="blue" onClick={onClose}>
Kembali Ke Permainan
</Button>
<Button
onClick={() => {
restartGame();
updateSettings({
computerDifficulty: computerDifficultySelection,
userPlayer: userPlayerSelection,
});
onClose();
}}
>
Mulai Permainan Baru
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}

View File

@ -0,0 +1,12 @@
import { HStack } from '@chakra-ui/react';
import { Logo } from './Logo';
import { Navigation } from './Navigation';
export function Header() {
return (
<HStack justify="space-between">
<Logo />
<Navigation />
</HStack>
);
}

View File

@ -0,0 +1,20 @@
import NextLink from 'next/link';
import { Heading, HStack } from '@chakra-ui/react';
import { DraughtsCrown } from '../draughts/board/views/DraughtsCrown';
export function Logo() {
return (
<NextLink href="/" passHref>
<HStack as="a">
<DraughtsCrown
bg="black"
padding={1}
borderRadius="50%"
height="1.5em"
width="1.5em"
/>
<Heading fontSize="2xl">Givan Checkers</Heading>
</HStack>
</NextLink>
);
}

View File

@ -0,0 +1,77 @@
import { useRef } from 'react';
import {
HStack,
VStack,
IconButton,
Link,
useDisclosure,
Drawer,
DrawerBody,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
DrawerHeader,
} from '@chakra-ui/react';
import { HamburgerIcon } from '@chakra-ui/icons';
const pages = [
{'url':'rules','name' : 'Peraturan'},
{'url':'strategies','name': 'Strategi'},
{'url':'history','name':'Sejarah'}
];
export function Navigation() {
const { isOpen, onOpen, onClose } = useDisclosure();
const btnRef = useRef();
return (
<>
<HStack as="nav" display={['none', 'none', 'block']} spacing={[2, 2, 4]}>
{pages.map((page) => (
<Link
key={`navlink-desktop-${page.url}`}
fontSize="lg"
fontWeight="bold"
href={`/${page.url}`}
>
{page.name}
</Link>
))}
</HStack>
<IconButton
ref={btnRef}
display={['block', 'block', 'none']}
aria-label="Open Menu"
icon={<HamburgerIcon />}
onClick={onOpen}
size="sm"
/>
<Drawer
finalFocusRef={btnRef}
isOpen={isOpen}
onClose={onClose}
placement="right"
size="xs"
>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Explore</DrawerHeader>
<DrawerBody>
<VStack as="nav">
{pages.map((page) => (
<Link
key={`navlink-mobile-${page}`}
fontSize="lg"
fontWeight="bold"
href={`/${page}`}
>
{page}
</Link>
))}
</VStack>
</DrawerBody>
</DrawerContent>
</Drawer>
</>
);
}

View File

@ -0,0 +1,66 @@
import { Grid, GridItem } from '@chakra-ui/react';
import PropTypes from 'prop-types';
import { Header } from '../header/Header';
import { LeftSidebarAdvert } from '../adverts/LeftSidebarAdvert';
import { RightSidebarAdvert } from '../adverts/RightSidebarAdvert';
import { TopBannerAdvert } from '../adverts/TopBannerAdvert';
import { BottomBannerAdvert } from '../adverts/BottomBannerAdvert';
export function MainLayout(props) {
return (
<Grid
as="main"
rowGap={[3, 4]}
columnGap={[3, 4, 5, 6]}
templateRows={['0 auto auto auto auto 0', '0 auto auto 0']}
templateColumns={[
'0 minmax(0, 1fr) 0',
'0 minmax(4em, 1fr) min(78vh, 78vw) 0',
'0 minmax(4em, 1fr) min(78vh, 78vw) minmax(4em, 1fr) 0',
]}
>
<GridItem colSpan={1} colStart={[2, 3, 3]} rowStart={2}>
<Header />
</GridItem>
<GridItem colSpan={1} colStart={[2, 3]} rowStart={[4, 3]}>
{props.children}
</GridItem>
<GridItem
display={['block', 'none']}
colSpan={2}
colStart={2}
rowStart={3}
>
<TopBannerAdvert />
</GridItem>
<GridItem
display={['block', 'none']}
colSpan={2}
colStart={2}
rowStart={6}
>
<BottomBannerAdvert />
</GridItem>
<GridItem
display={['none', 'block']}
colStart={2}
rowSpan={[2, 3, 4]}
rowStart={[3, 3, 2]}
>
<LeftSidebarAdvert />
</GridItem>
<GridItem
display={['none', 'none', 'block']}
colStart={5}
rowSpan={[2, 2, 4]}
rowStart={[3, 3, 2]}
>
<RightSidebarAdvert />
</GridItem>
</Grid>
);
}
MainLayout.propTypes = {
children: PropTypes.node,
};

10
jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"include": ["."],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@draughts/*": ["packages/draughts/*"]
}
},
"exclude": ["node_modules"]
}

48
next.config.js Normal file
View File

@ -0,0 +1,48 @@
/**
* @type {import('next').NextConfig}
*/
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
reactStrictMode: true,
async redirects() {
return [
{
destination: '/',
permanent: true,
source: '/index.html',
},
{
destination: '/',
permanent: true,
source: '/games_list.html',
},
{
destination: '/rules',
permanent: true,
source: '/the_rules_of_draughts.html',
},
{
destination: '/strategies',
permanent: true,
source: '/strategies_for_draughts.html',
},
{
destination: '/history',
permanent: true,
source: '/the_evolution_of_draughts.html',
},
{
destination: '/history',
permanent: true,
source: '/variations_for_the_game_of_draughts.html',
},
{
destination: '/history',
permanent: true,
source: '/the_history_of_draughts.html',
},
];
},
};

9799
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "draughts-web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@chakra-ui/icons": "^2.0.2",
"@chakra-ui/react": "^2.2.1",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"framer-motion": "^6.3.11",
"next": "^12.1.6",
"prop-types": "^15.8.1",
"react": "^18.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.1.0"
},
"devDependencies": {
"eslint": "^8.17.0",
"eslint-config-next": "^12.1.6",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-node": "^0.3.6",
"eslint-plugin-chakra-ui": "^0.6.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-sonarjs": "^0.13.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-unicorn": "^42.0.0",
"prettier": "^2.6.2"
}
}

View File

@ -0,0 +1,46 @@
import { quiescenceSearch } from './quiescence-search';
import { GameStates } from '@draughts/core';
const getShuffledArray = (arr) => {
const newArr = [...arr];
for (let i = newArr.length - 1; i > 0; i--) {
const rand = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
}
return newArr;
};
export function alphaBetaMove(board, depth) {
let recordE = Number.NEGATIVE_INFINITY;
let recordMove = null;
for (const move of getShuffledArray(board.moves)) {
const nextBoard = board.doMove(move);
const e = -alphaBetaSearch(
nextBoard,
depth - 1,
Number.NEGATIVE_INFINITY,
Number.POSITIVE_INFINITY
);
if (e >= recordE) {
recordE = e;
recordMove = move;
}
}
return recordMove;
}
export function alphaBetaSearch(board, depth, alpha, beta) {
if (depth === 0 || board.state !== GameStates.PLAYING)
return quiescenceSearch(board, alpha, beta);
for (const move of board.moves) {
const nextBoard = board.doMove(move);
const e = -alphaBetaSearch(nextBoard, depth - 1, -beta, -alpha);
if (e >= beta) return beta;
alpha = Math.max(e, alpha);
}
return alpha;
}

View File

@ -0,0 +1,101 @@
import {
eachCell,
pieceIsPlayer,
pieceIsQueen,
BOARD_SIZE,
Players,
GameStates,
} from '@draughts/core';
const winnerMap = {
[GameStates.WHITE_WON]: Players.WHITE,
[GameStates.BLACK_WON]: Players.BLACK,
};
export function evaluateBoard(board) {
if (board.state === GameStates.DRAW) {
return 0;
}
if (board.state !== GameStates.PLAYING) {
const winner = winnerMap[board.state];
return board.playerToMove === winner
? Number.POSITIVE_INFINITY
: Number.NEGATIVE_INFINITY;
}
if (isEndgame(board)) {
return evaluateEndgamePosition(board);
}
return evaluateRegularPosition(board);
}
function isEndgame(board) {
for (const { piece } of eachCell(board.position)) {
if (!pieceIsPlayer(piece, Players.NONE) && !pieceIsQueen(piece)) {
return false;
}
}
return true;
}
function evaluateEndgamePosition(board) {
let playerPieces = 0;
let opponentPieces = 0;
let distances = 0;
for (const { cell, piece } of eachCell(board.position)) {
if (pieceIsPlayer(piece, board.playerToMove)) {
playerPieces += 1;
distances += calculateDistances(board, cell);
} else if (!pieceIsPlayer(piece, Players.NONE)) {
opponentPieces += 1;
}
}
if (playerPieces >= opponentPieces) {
return -distances;
}
return distances;
}
function evaluateRegularPosition(board) {
let e = 0;
for (const { cell, piece } of eachCell(board.position)) {
const pieceEvaluation = evaluatePiece(cell, piece);
if (pieceIsPlayer(piece, board.playerToMove)) {
e += pieceEvaluation;
} else if (!pieceIsPlayer(piece, Players.NONE)) {
e -= pieceEvaluation;
}
}
return e;
}
function evaluatePiece(cell, piece) {
let e = 20;
if (pieceIsQueen(piece)) {
e += 40;
} else if (pieceIsPlayer(piece, Players.WHITE)) {
e += BOARD_SIZE - 1 - cell.row;
} else if (pieceIsPlayer(piece, Players.BLACK)) {
e += cell.row;
}
return e;
}
function calculateDistances(board, baseCell) {
let distances = 0;
for (const { cell, piece } of eachCell(board.position)) {
if (
!pieceIsPlayer(piece, board.playerToMove) &&
!pieceIsPlayer(piece, Players.NONE)
) {
distances += euclideanDistance(baseCell, cell);
}
}
return distances;
}
function euclideanDistance(a, b) {
const rowSquare = Math.pow(a.row - b.row, 2);
const colSquare = Math.pow(a.col - b.col, 2);
const distanceFloat = Math.sqrt(rowSquare + colSquare);
return Math.ceil(distanceFloat);
}

View File

@ -0,0 +1 @@
export { alphaBetaMove } from './alpha-beta-search';

View File

@ -0,0 +1,543 @@
import {
eachCell,
pieceIsPlayer,
pieceIsQueen,
BOARD_SIZE,
Players,
GameStates,
} from '@draughts/core';
const winnerMap = {
[GameStates.WHITE_WON]: Players.WHITE,
[GameStates.BLACK_WON]: Players.BLACK,
};
export function negascout_search(board) {
if (board.state === GameStates.DRAW) {
return 0;
}
if (board.state !== GameStates.PLAYING) {
const winner = winnerMap[board.state];
return board.playerToMove === winner
? Number.POSITIVE_INFINITY
: Number.NEGATIVE_INFINITY;
}
if (isEndgame(board)) {
return evaluateEndgamePosition(board);
}
return evaluateRegularPosition(board);
}
function eval_board(Board, pieceType, restrictions) {
let score = 0;
const min_r = restrictions[0];
const min_c = restrictions[1];
const max_r = restrictions[2];
const max_c = restrictions[3];
for (let row = min_r; row < max_r + 1; row++) {
for (let column = min_c; column < max_c + 1; column++) {
if (Board[row][column] == pieceType) {
let block = 0;
let piece = 1;
// left
if (column === 0 || Board[row][column - 1] !== 0) {
block++;
}
// pieceNum
for (column++; column < Columns && Board[row][column] === pieceType; column++) {
piece++;
}
// right
if (column === Columns || Board[row][column] !== 0) {
block++;
}
score += evaluateblock(block, piece);
}
}
}
for (let column = min_c; column < max_c + 1; column++) {
for (let row = min_r; row < max_r + 1; row++) {
if (Board[row][column] == pieceType) {
let block = 0;
let piece = 1;
// left
if (row === 0 || Board[row - 1][column] !== 0) {
block++;
}
// pieceNum
for (row++; row < Rows && Board[row][column] === pieceType; row++) {
piece++;
}
// right
if (row === Rows || Board[row][column] !== 0) {
block++;
}
score += evaluateblock(block, piece);
}
}
}
for (let n = min_r; n < (max_c - min_c + max_r); n += 1) {
let r = n;
let c = min_c;
while (r >= min_r && c <= max_c) {
if (r <= max_r) {
if (Board[r][c] === pieceType) {
let block = 0;
let piece = 1;
// left
if (c === 0 || r === Rows - 1 || Board[r + 1][c - 1] !== 0) {
block++;
}
// pieceNum
r--;
c++;
for (; r >= 0 && Board[r][c] === pieceType; r--) {
piece++;
c++
}
// right
if (r < 0 || c === Columns || Board[r][c] !== 0) {
block++;
}
score += evaluateblock(block, piece);
}
}
r -= 1;
c += 1;
}
}
for (let n = min_r - (max_c - min_c); n <= max_r; n++) {
let r = n;
let c = min_c;
while (r <= max_r && c <= max_c) {
if (r >= min_r && r <= max_r) {
if (Board[r][c] === pieceType) {
let block = 0;
let piece = 1;
// left
if (c === 0 || r === 0 || Board[r - 1][c - 1] !== 0) {
block++;
}
// pieceNum
r++;
c++;
for (; r < Rows && Board[r][c] == pieceType; r++) {
piece++;
c++;
}
// right
if (r === Rows || c === Columns || Board[r][c] !== 0) {
block++;
}
score += evaluateblock(block, piece);
}
}
r += 1;
c += 1;
}
}
return score;
}
function evaluateblock(blocks, pieces) {
if (blocks === 0) {
switch (pieces) {
case 1:
return LiveOne;
case 2:
return LiveTwo;
case 3:
return LiveThree;
case 4:
return LiveFour;
default:
return Five;
}
} else if (blocks === 1) {
switch (pieces) {
case 1:
return DeadOne;
case 2:
return DeadTwo;
case 3:
return DeadThree;
case 4:
return DeadFour;
default:
return Five;
}
} else {
if (pieces >= 5) {
return Five;
} else {
return 0
}
}
}
function check_directions(arr) {
for (let i = 0; i < arr.length - 4; i++) {
if (arr[i] !== 0) {
if (arr[i] === arr[i + 1] && arr[i] === arr[i + 2] && arr[i] === arr[i + 3] && arr[i] === arr[i + 4]) {
return true
}
}
}
}
function get_directions(Board, x, y) {
const Directions = [[],[],[],[]];
for (let i = -4; i < 5; i++) {
if (x + i >= 0 && x + i <= Rows - 1) {
Directions[0].push(Board[x + i][y])
if (y + i >= 0 && y + i <= Columns - 1) {
Directions[2].push(Board[x + i][y + i])
}
}
if (y + i >= 0 && y + i <= Columns - 1) {
Directions[1].push(Board[x][y + i])
if (x - i >= 0 && x - i <= Rows - 1) {
Directions[3].push(Board[x - i][y + i])
}
}
}
return Directions
}
function checkwin(Board, x, y) {
const Directions = get_directions(Board, x, y)
for (let i = 0; i < 4; i++) {
if (check_directions(Directions[i])) {
return true
}
}
}
function remoteCell(Board, r, c) {
for (let i = r - 2; i <= r + 2; i++) {
if (i < 0 || i >= Rows) continue;
for (let j = c - 2; j <= c + 2; j++) {
if (j < 0 || j >= Columns) continue;
if (Board[i][j] !== 0) return false;
}
}
return true;
}
function Get_restrictions(Board) {
let min_r = Infinity;
let min_c = Infinity;
let max_r = -Infinity;
let max_c = -Infinity;
for (let i = 0; i < Rows; i++) {
for (let j = 0; j < Columns; j++) {
if (Board[i][j] !== 0) {
min_r = Math.min(min_r, i)
min_c = Math.min(min_c, j)
max_r = Math.max(max_r, i)
max_c = Math.max(max_c, j)
}
}
}
if (min_r - 2 < 0) {
min_r = 2;
}
if (min_c - 2 < 0) {
min_c = 2;
}
if (max_r + 2 >= Rows) {
max_r = Rows - 3;
}
if (max_c + 2 >= Columns) {
max_c = Columns - 3;
}
return [min_r, min_c, max_r, max_c]
}
function Change_restrictions(restrictions, i, j) {
let min_r = restrictions[0];
let min_c = restrictions[1];
let max_r = restrictions[2];
let max_c = restrictions[3];
if (i < min_r) {
min_r = i
} else if (i > max_r) {
max_r = i
}
if (j < min_c) {
min_c = j
} else if (j > max_c) {
max_c = j
}
if (min_r - 2 < 0) {
min_r = 2;
}
if (min_c - 2 < 0) {
min_c = 2;
}
if (max_r + 2 >= Rows) {
max_r = Rows - 3;
}
if (max_c + 2 >= Columns) {
max_c = Columns - 3;
}
return [min_r, min_c, max_r, max_c]
}
function compare(a, b) {
if (a.score < b.score)
return 1;
if (a.score > b.score)
return -1;
return 0;
}
function BoardGenerator(restrictions, Board, player) {
const availSpots_score = []; //c is j r is i;
const min_r = restrictions[0];
const min_c = restrictions[1];
const max_r = restrictions[2];
const max_c = restrictions[3];;
for (let i = min_r - 2; i <= max_r + 2; i++) {
for (let j = min_c - 2; j <= max_c + 2; j++) {
if (Board[i][j] === 0 && !remoteCell(Board, i, j)) {
const move = {}
move.i = i;
move.j = j;
move.score = evaluate_move(Board, i, j, player)
if (move.score === WIN_DETECTED) {
return [move]
}
availSpots_score.push(move)
}
}
}
availSpots_score.sort(compare);
// return availSpots_score.slice(0,20)
return availSpots_score;
}
function evaluate_direction(direction_arr, player) {
let score = 0;
for (let i = 0;(i + 4) < direction_arr.length; i++) {
let you = 0;
let enemy = 0;
for (let j = 0; j <= 4; j++) {
if (direction_arr[i + j] === player) {
you++
} else if (direction_arr[i + j] === -player) {
enemy++
}
}
score += evalff(get_seq(you, enemy));
if ((score >= 800000)) {
return WIN_DETECTED;
}
}
return score
}
function get_seq(y, e) {
if (y + e === 0) {
return 0;
}
if (y !== 0 && e === 0) {
return y
}
if (y === 0 && e !== 0) {
return -e
}
if (y !== 0 && e !== 0) {
return 17
}
}
function evaluate_move(Board, x, y, player) {
let score = 0;
const Directions = get_directions(Board, x, y);
let temp_score;
for (let i = 0; i < 4; i++) {
temp_score = evaluate_direction(Directions[i], player);
if (temp_score === WIN_DETECTED) {
return WIN_DETECTED
} else {
score += temp_score
}
}
return score;
}
function evaluate_state(Board, player, hash, restrictions) {
const black_score = eval_board(Board, -1, restrictions);
const white_score = eval_board(Board, 1, restrictions);
let score = 0;
if (player == -1) {
score = (black_score - white_score);
} else {
score = (white_score - black_score);
}
StateCache.set(hash,score);
StateCachePuts++;
return score;
}
function random32() {
let o = new Uint32Array(1);
self.crypto.getRandomValues(o);
return o[0];
}
function Table_init() {
for (let i = 0; i < Rows; i++) {
Table[i] = [];
for (let j = 0; j < Columns; j++) {
Table[i][j] = []
Table[i][j][0] = random32(); //1
Table[i][j][1] = random32(); //2
}
}
}
function hash(board) {
let h = 0;
let p;
for (let i = 0; i < Rows; i++) {
for (let j = 0; j < Columns; j++) {
let Board_value = board[i][j];
if (Board_value !== 0) {
if (Board_value === -1) {
p = 0
} else {
p = 1
}
h = h ^ Table[i][j][p];
}
}
}
return h;
}
function update_hash(hash, player, row, col) {
if (player === -1) {
player = 0
} else {
player = 1
}
hash = hash ^ Table[row][col][player];
return hash
}
function negascout(newBoard, player, depth, alpha, beta, hash, restrictions, last_i, last_j) {
const alphaOrig = alpha;
const CacheNode =Cache.get(hash)
if ((CacheNode !== undefined) && (CacheNode.depth >= depth)) {
CacheHits++;
const score = CacheNode.score;
if (CacheNode.Flag === 0) {
CacheCutoffs++;
return score
}
if (CacheNode.Flag === -1) {
alpha = Math.max(alpha, score);
} else if (CacheNode.Flag === 1) {
beta = Math.min(beta, score);
}
if (alpha >= beta) {
CacheCutoffs++
return score
}
}
fc++
if (checkwin(newBoard, last_i, last_j)) {
return -2000000 + (MaximumDepth - depth)
}
if (depth === 0) {
const StateCacheNode=StateCache.get(hash);
if (StateCacheNode !== undefined) {
StateCacheHits++
return StateCacheNode
}
return evaluate_state(newBoard, player, hash, restrictions)
}
const availSpots = BoardGenerator(restrictions, newBoard, player);
if (availSpots.length === 0) {
return 0;
}
let b = beta;
let bestscore = -Infinity;
const bestMove={};
for (let y = 0; y < availSpots.length; y++) {
let i = availSpots[y].i;
let j = availSpots[y].j;
const newHash = update_hash(hash, player, i, j)
newBoard[i][j] = player;
const restrictions_temp = Change_restrictions(restrictions, i, j)
let score = -negascout(newBoard, -player, depth - 1, -b, -alpha, newHash, restrictions_temp, i, j)
if (score > alpha && score < beta && y > 0) {
score = -negascout(newBoard, -player, depth - 1, -beta, -score, newHash, restrictions_temp, i, j)
}
if (score > bestscore) {
bestscore = score
if (depth === MaximumDepth) {
bestMove.i=i
bestMove.j=j
bestMove.score=score;
}
}
newBoard[i][j] = 0;
alpha = Math.max(alpha, score)
if (alpha >= beta) {
break;
}
b = alpha + 1;
}
CachePuts++
const obj={score: bestscore,depth:depth};
if (bestscore <= alphaOrig) {
obj.Flag = 1
} else if (bestscore >= b) {
obj.Flag = -1
} else {
obj.Flag = 0
}
Cache.set(hash,obj);
if (depth == MaximumDepth) {
return bestMove
} else {
return bestscore
}
}
function iterative_negascout(player, Board, depth) {
let bestmove;
let i = 2;
while (i !== depth + 2) {
MaximumDepth = i;
bestmove = negascout(Board, player, MaximumDepth, -Infinity, Infinity, hash(Board), Get_restrictions(Board), 0, 0)
// Set_last_best(bestmove)
console.log(MaximumDepth)
console.log(bestmove)
let t11 = performance.now();
console.log((t11 - t00) / 1000)
if (bestmove.score > 1999900) {
break;
}
i += 2;
}
return bestmove
}

View File

@ -0,0 +1,20 @@
import { evaluateBoard } from './evaluate-board';
export function quiescenceSearch(board, alpha, beta) {
const baseE = evaluateBoard(board);
if (baseE >= beta) return beta;
alpha = Math.max(baseE, alpha);
for (const move of board.moves) {
if (move.captures.length <= 0) continue;
const nextBoard = board.doMove(move);
const e = -quiescenceSearch(nextBoard, -beta, -alpha);
if (e >= beta) return beta;
alpha = Math.max(e, alpha);
}
return alpha;
}

View File

@ -0,0 +1,178 @@
import {
cellIsEmpty,
queenPiece,
eachCell,
pieceIsPlayer,
pieceIsQueen,
shouldQueen,
clonePosition,
filterToLongestCaptures,
cellWithinBounds,
} from './utilities';
import { Players, Pieces, GameStates } from '@draughts/core';
export class Board {
constructor(position, playerToMove, firstMove = true) {
this.position = position;
this.playerToMove = playerToMove;
this.firstMove = firstMove;
this.direction = this.playerToMove === Players.WHITE ? -1 : 1;
this.moves = this._computeMoves();
this.state = this._computeState();
}
_computeState() {
if (this.moves.length > 0) {
return GameStates.PLAYING;
}
let blackPieces = 0;
let whitePieces = 0;
for (const { piece } of eachCell(this.position)) {
if (pieceIsPlayer(piece, Players.WHITE)) {
whitePieces = whitePieces + 1;
} else if (pieceIsPlayer(piece, Players.BLACK)) {
blackPieces = blackPieces + 1;
}
}
this.playerToMove = Players.NONE;
if (blackPieces === 0) {
return GameStates.WHITE_WON;
}
if (whitePieces === 0) {
return GameStates.BLACK_WON;
}
return GameStates.DRAW;
}
_computeMoves() {
const starts = this._getStarts();
const captures = this._getCaptures(starts);
if (captures.length > 0) return captures;
return this._getMoves(starts);
}
_getStarts() {
const starts = [];
for (const { cell, piece } of eachCell(this.position)) {
if (pieceIsPlayer(piece, this.playerToMove)) {
starts.push(cell);
}
}
return starts;
}
_getCaptures(starts) {
let captures = [];
for (const start of starts) {
captures = [
...captures,
...this._getNextCaptures(this.position, {
captures: [],
path: [start],
}),
];
}
return filterToLongestCaptures(captures);
}
// eslint-disable-next-line sonarjs/cognitive-complexity
_getNextCaptures(initialPosition, move) {
const captures = [];
const start = move.path.at(-1);
const piece = initialPosition[start.row][start.col];
const possibleDRow = pieceIsQueen(piece) ? [-1, 1] : [1];
for (const dRow of possibleDRow) {
for (const dCol of [-1, 1]) {
const middle = {
col: start.col + dCol * this.direction,
row: start.row + dRow * this.direction,
};
const end = {
col: middle.col + dCol * this.direction,
row: middle.row + dRow * this.direction,
};
if (!cellWithinBounds(end) || !cellIsEmpty(initialPosition, end))
continue;
const middlePiece = initialPosition[middle.row][middle.col];
if (
pieceIsPlayer(middlePiece, Players.NONE) ||
pieceIsPlayer(middlePiece, this.playerToMove)
)
continue;
const queened = shouldQueen(end, piece);
const searchPosition = clonePosition(initialPosition);
searchPosition[start.row][start.col] = Pieces.NONE;
searchPosition[middle.row][middle.col] = Pieces.NONE;
searchPosition[end.row][end.col] = queened ? queenPiece(piece) : piece;
const nextMove = {
captures: [...move.captures, middle],
path: [...move.path, end],
};
captures.push(nextMove);
if (!queened)
captures.push(...this._getNextCaptures(searchPosition, nextMove));
}
}
return filterToLongestCaptures(captures);
}
_getMoves(starts) {
const moves = [];
for (const start of starts) {
const piece = this.position[start.row][start.col];
const possibleDeltaRow = pieceIsQueen(piece) ? [-1, 1] : [1];
for (const deltaRow of possibleDeltaRow) {
for (const deltaCol of [-1, 1]) {
const end = {
col: start.col + deltaCol * this.direction,
row: start.row + deltaRow * this.direction,
};
if (cellWithinBounds(end) && cellIsEmpty(this.position, end)) {
moves.push({ captures: [], path: [start, end] });
}
}
}
}
return moves;
}
doMove({ path, captures }) {
const start = path.at(0);
const startPiece = this.position[start.row][start.col];
const end = path.at(-1);
const endPiece = shouldQueen(end, startPiece)
? queenPiece(startPiece)
: startPiece;
const newPosition = clonePosition(this.position);
newPosition[path.at(0).row][path.at(0).col] = Pieces.NONE;
newPosition[end.row][end.col] = endPiece;
for (const capture of captures) {
newPosition[capture.row][capture.col] = Pieces.NONE;
}
const newPlayerToMove =
this.playerToMove === Players.WHITE ? Players.BLACK : Players.WHITE;
return new Board(newPosition, newPlayerToMove, false);
}
}

View File

@ -0,0 +1,20 @@
export const Pieces = {
BLACK: 'b',
BLACK_QUEEN: 'bq',
NONE: '',
WHITE: 'w',
WHITE_QUEEN: 'wq',
};
export const GameStates = {
BLACK_WON: 'b',
DRAW: 'd',
PLAYING: 'p',
WHITE_WON: 'w',
};
export const Players = {
BLACK: 'b',
NONE: '',
WHITE: 'w',
};

View File

@ -0,0 +1,5 @@
export { Players, Pieces, GameStates } from './enums';
export { INITIAL_POSITION } from './positions';
export const BOARD_SIZE = 8;

View File

@ -0,0 +1,86 @@
import { Pieces } from './enums';
export const INITIAL_POSITION = [
[
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
],
[
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
],
[
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
],
[
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
],
[
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
],
[
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
],
[
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
],
[
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
],
];

View File

@ -0,0 +1,22 @@
export {
Players,
GameStates,
Pieces,
INITIAL_POSITION,
BOARD_SIZE,
} from './constants';
export { Board } from './board';
export {
cellIsEmpty,
queenPiece,
eachCell,
compareCells,
pieceIsPlayer,
pieceIsQueen,
shouldQueen,
clonePosition,
filterToLongestCaptures,
cellWithinBounds,
} from './utilities';

View File

@ -0,0 +1,81 @@
import { BOARD_SIZE, Pieces, Players } from '@draughts/core';
const isQueenMap = {
[Pieces.NONE]: false,
[Pieces.BLACK]: false,
[Pieces.WHITE]: false,
[Pieces.BLACK_QUEEN]: true,
[Pieces.WHITE_QUEEN]: true,
};
export const pieceIsQueen = (piece) => isQueenMap[piece];
const isPlayerMap = {
[Players.NONE]: new Set([Pieces.NONE]),
[Players.WHITE]: new Set([Pieces.WHITE, Pieces.WHITE_QUEEN]),
[Players.BLACK]: new Set([Pieces.BLACK, Pieces.BLACK_QUEEN]),
};
export const pieceIsPlayer = (piece, player) => {
return isPlayerMap[player].has(piece);
};
export const cellWithinBounds = ({ row, col }) =>
row < BOARD_SIZE && row >= 0 && col < BOARD_SIZE && col >= 0;
export const cellIsEmpty = (position, { row, col }) =>
position[row][col] === Pieces.NONE;
export function* eachCell(position) {
for (const [row, rowArray] of position.entries()) {
for (const [col, piece] of rowArray.entries()) {
yield { cell: { col, row }, piece };
}
}
}
const crownQueenMap = {
[Pieces.BLACK]: Pieces.BLACK_QUEEN,
[Pieces.WHITE]: Pieces.WHITE_QUEEN,
[Pieces.BLACK_QUEEN]: Pieces.BLACK_QUEEN,
[Pieces.WHITE_QUEEN]: Pieces.WHITE_QUEEN,
};
export const queenPiece = (piece) => crownQueenMap[piece];
export const shouldQueen = (cell, piece) => {
if (pieceIsQueen(piece)) return false;
return (
(pieceIsPlayer(piece, Players.WHITE) && cell.row === 0) ||
(pieceIsPlayer(piece, Players.BLACK) && cell.row === BOARD_SIZE - 1)
);
};
export const filterToLongestCaptures = (moves) => {
let longestCaptures = [];
let recordLength = 0;
for (const move of moves) {
if (move.captures.length > recordLength) {
longestCaptures = [move];
recordLength = move.captures.length;
} else if (move.captures.length === recordLength) {
longestCaptures.push(move);
}
}
return longestCaptures;
};
export const clonePosition = (position) => [...position.map((row) => [...row])];
export const compareCells = (cellOne, cellTwo) => {
if (!cellOne || !cellTwo) return false;
return cellOne.row === cellTwo.row && cellOne.col === cellTwo.col;
};
const formatPlayerMap = {
[Players.NONE]: 'none',
[Players.WHITE]: 'white',
[Players.BLACK]: 'black',
};
export const formatPlayer = (player) => formatPlayerMap[player];

73
pages/_app.js Normal file
View File

@ -0,0 +1,73 @@
import { ChakraProvider } from '@chakra-ui/react';
import Head from 'next/head';
import Script from 'next/script';
// eslint-disable-next-line react/prop-types
export default function App(props) {
return (
<>
<Script
strategy="lazyOnload"
src="https://www.googletagmanager.com/gtag/js?id=G-N7EVGCFBVC"
/>
<Script
id="google-analytics"
strategy="lazyOnload"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || []; function
gtag(){dataLayer.push(arguments);} gtag('js', new Date());
gtag('config', 'G-N7EVGCFBVC', { page_path:
window.location.pathname, });
`,
}}
/>
<Head>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#d6c420" />
<meta name="msapplication-TileColor" content="#ffc40d" />
<meta name="theme-color" content="#ffffff" />
<meta property="og:type" content="website" />
<meta
property="og:title"
content="Free Online Draughts Game - play against the computer"
/>
<meta property="og:url" content="https://draughts.org/" />
<meta property="og:site_name" content="Draughts" />
<meta
property="og:description"
content="Play draughts free online against the computer. Read about Rules and Strategies for Draughts"
/>
<meta name="robots" content="index, follow" />
<meta
name="description"
content="Play draughts free online against the computer. Read about Rules and Strategies for Draughts"
/>
<meta
name="keywords"
content="draughts,checkers,free draughts,free checkers,board games,board gaming online,play draughts online,play checkers online,play free games online,free games,online games,online checkers,online draughts"
/>
</Head>
<ChakraProvider>
<props.Component {...props.pageProps} />
</ChakraProvider>
</>
);
}

15
pages/_document.js Normal file
View File

@ -0,0 +1,15 @@
import NextDocument, { Html, Head, Main, NextScript } from 'next/document';
export default class Document extends NextDocument {
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

82
pages/history.jsx Normal file
View File

@ -0,0 +1,82 @@
import Head from 'next/head';
import { Container, Text, VStack } from '@chakra-ui/react';
import Image from 'next/image';
import { MainLayout } from '../components/layout/MainLayout';
import aquerqueImage from '../public/history/aquerque.jpeg';
import draughts1700sImage from '../public/history/draughts-1700s.webp';
export default function History() {
return (
<MainLayout>
<Head>
<title>Givan Dam - Main Dam</title>
</Head>
<VStack p={[3, 0]} spacing={6}>
<Text>
Penggalian arkeologi di Irak menemukan bentuk paling awal yang diketahui
permainan Draf. Penanggalan karbon digunakan untuk menentukan umur
permainan kuno, dan tampaknya berasal dari sekitar 3000 SM. Itu
papan dan jumlah potongan yang digunakan berbeda dari
Papan draf dan potongan digunakan saat ini.
</Text>
<Text>
Sekitar 1400 SM, orang Mesir kuno menggunakan papan berukuran 5 x 5 untuk memainkan a
permainan yang disebut Aquerque. Permainan ini sangat populer selama ini dan
itu dimainkan di seluruh peradaban barat selama ribuan tahun.
</Text>
<Container maxW="md">
<Image src={aquerqueImage} alt="ancient 5x5 egyption chessboard" />
</Container>
<Text>
Sekitar tahun 1100 A.D., permainan Aquerque berubah ketika seorang Prancis memainkannya
itu di papan catur menggunakan 12 buah untuk setiap pemain. Nama dari
permainan juga berubah. Itu dikenal sebagai "Fierges."
</Text>
<Text>
Evolusi Draf berikutnya terjadi ketika peraturan berubah lagi,
membuatnya wajib untuk melompati Draf untuk maju ke seluruh papan.
Versi yang lebih baru ini lebih menantang daripada yang lama. Versi lama
dianggap lebih lambat dan kurang menantang, dan menjadi sosial
permainan yang dimainkan oleh para wanita pada zaman itu dan disebut La Jeu Pleasant De
Dames, (permainan wanita yang menyenangkan). Yang baru, lebih agresif
bentuk permainan tersebut dikenal dengan nama Jeu Force (Play Force).
</Text>
<Text>
Draf akhirnya diekspor dari Prancis ke Inggris, Spanyol, dan
Amerika. Di Spanyol sekitar pertengahan tahun 1500-an, buku mulai ada
ditulis tentang Draf. Pada tahun 1756, William Payne, seorang matematikawan di
Inggris, menulis bukunya sendiri tentang Draf.
</Text>
<Container maxW="md">
<Image
src={draughts1700sImage}
alt="draughts being played in the 1700s"
/>
</Container>
<Text>
Pada tahun 1847, kejuaraan dunia Draf pertama berlangsung. Seiring waktu
berlalu, menjadi jelas bahwa game tersebut menghadirkan bukaan yang memberi
keuntungan satu pihak atas pihak lain. Ada dua batasan gerakan
dibuat di mana permainan dimulai dengan gaya acak. Dua ini
pembatasan bergerak sebagian besar digunakan oleh pemain ahli. Di zaman modern
Turnamen draf, tiga batasan gerakan digunakan.
</Text>
<Text>
Seiring kemajuan teknologi, tidak lama kemudian programmer komputer
mulai mengembangkan game Draft yang sangat mendasar yang dapat dimainkan
komputer. Alan Turing membuat game Draf yang belum sempurna di atas kertas
karena komputer pada saat itu belum cukup berkembang untuk menjalankannya
Program draf. Pada tahun 1952, Arthur L. Samuel membuat Draf pertama
program yang sebenarnya bisa dimainkan di komputer. Dari titik itu
maju, game Draf komputer telah meningkat dalam kecepatan dan fungsi.
</Text>
<Text>
Program Draf saat ini membutuhkan perencanaan yang kurang strategis dan lebih banyak lagi
kemampuan pencarian data komputer. Program Draf menggunakan database
pencarian yang menampilkan semua kemungkinan kombinasi ketika ada beberapa bagian
tertinggal di papan.
</Text>
</VStack>
</MainLayout>
);
}

38
pages/index.jsx Normal file
View File

@ -0,0 +1,38 @@
import Head from 'next/head';
import { Divider, Heading, HStack, VStack } from '@chakra-ui/react';
import { DraughtsMenuView } from '../components/draughts/settings/views/DraughtsMenu';
import { DraughtsProvider } from '../components/draughts/DraughtsContext';
import { DraughtsBoard } from '../components/draughts/board/views/DraughtsBoard';
import { DraughtsGameInfoView } from '../components/draughts/DraughtsGameInfo';
import { ComputerDifficulty } from '../components/draughts/settings/constants/computer-difficulty';
import { MainLayout } from '../components/layout/MainLayout';
import { DraughtsRulesContent } from '../components/content/DraughtsRulesContent';
import { INITIAL_POSITION, Players } from '@draughts/core';
export default function Home() {
return (
<MainLayout>
<Head>
<title>Givan Checkers - Main Checkers Secara Gratis</title>
</Head>
<DraughtsProvider
settings={{
computerDifficulty: ComputerDifficulty.MEDIUM,
userPlayer: Players.WHITE,
}}
board={{ playerToMove: Players.WHITE, position: INITIAL_POSITION }}
>
<VStack spacing={4}>
<DraughtsBoard />
<HStack>
<DraughtsMenuView />
<DraughtsGameInfoView />
</HStack>
<Divider />
<Heading size="sm">Peraturan Main</Heading>
<DraughtsRulesContent />
</VStack>
</DraughtsProvider>
</MainLayout>
);
}

25
pages/rules.jsx Normal file
View File

@ -0,0 +1,25 @@
/* eslint-disable unicorn/filename-case */
import { AspectRatio } from '@chakra-ui/react';
import Head from 'next/head';
import { DraughtsRulesContent } from '../components/content/DraughtsRulesContent';
import { MainLayout } from '../components/layout/MainLayout';
export default function History() {
return (
<MainLayout>
<Head>
<title>Givan Checkers - Peraturan &amp; Cara Bermain</title>
</Head>
<DraughtsRulesContent />
{/*<AspectRatio maxW="md" radio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/PgNN6CdkYXs"
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</AspectRatio>*/}
</MainLayout>
);
}

86
pages/strategies.jsx Normal file
View File

@ -0,0 +1,86 @@
import Head from 'next/head';
import { AspectRatio, Text, VStack } from '@chakra-ui/react';
import { MainLayout } from '../components/layout/MainLayout';
export default function History() {
return (
<MainLayout>
<Head>
<title>Givan Checkers - Strategi &amp; Pembukaan</title>
</Head>
<VStack p={[3, 0]} spacing={6}>
<Text>
Draf, juga dikenal sebagai checkers, adalah permainan papan strategi yang memiliki
telah ada selama ribuan tahun. Ada banyak varian, tapi
versi paling umum dimainkan di papan kotak-kotak 8x8. Keduanya
permainan pemain terdiri dari dua belas buah (pria, catur, draft)per
samping. Potongan dimulai dari tiga baris pertama pada warna hitam/gelap
kotak saja. Pria hanya bisa maju secara diagonal &quot;melompat&quot; lebih
bagian itu dan mendarat di ruang kosong yang berdekatan. Setelah semua pria memiliki
ditangkap, permainan dimenangkan. Permainan juga bisa dimenangkan melalui
menghalangi kemampuan lawan untuk bergerak. Ada banyak taktik yang berguna
untuk meningkatkan peluang keberhasilan Anda.
</Text>
<Text>
Menobatan, atau menjadikan raja, sangat meningkatkan kekuatan dan portabilitas
laki-laki Anda. Jika Anda bisa mendapatkan bagian ke garis dasar pemain lain, itu
bisa &quot;dimahkotai&quot;. Sepotong lain ditempatkan di atas
membedakannya dari draf biasa. Raja sekarang dapat dipindahkan keduanya
maju dan mundur, secara efektif menggandakan jangkauannya.
</Text>
<Text>
Karena pria yang menangkap membutuhkan kotak kosong untuk dilompati, memang begitu
bijaksana untuk memindahkan Anda potongan secara massal. Cobalah untuk tidak meninggalkan potongan individu
terpencil. Pindahkan lebih sedikit potongan dalam formasi ketat.
</Text>
<Text>
Cobalah untuk meninggalkan orang-orang garis dasar Anda di stasiun selama mungkin.
Kotak bebas apa pun berpotensi untuk penobatan oposisi. Mereka akan
tidak dapat membuat raja jika mereka tidak bisa mendarat di sana.
</Text>
<Text>
Draf, seperti permainan papan lainnya, bekerja berdasarkan prinsip umum
bertukar potongan setiap kali ada yang di depan. Keuntungan material dari memiliki
hanya satu orang tambahan menjadi lebih signifikan secara proporsional semakin sedikit
potongan tetap. Peluang mahkota akan meningkat pesat. Satu
peringatan untuk prinsip umum ini, adalah mengabaikan keunggulan posisi
untuk keuntungan materi buta. Seorang raja bisa sangat mengubah jalannya permainan
dengan cepat.
</Text>
<Text>
Mengorbankan draf bisa tampak sembrono atau ceroboh. Tapi ini
strategi dapat digunakan untuk menarik keuntungan posisional. Untuk menghapus a
potongan dasar dalam persiapan untuk penobatan misalnya, akan menjadi a
penggunaan taktik pengorbanan yang baik.
</Text>
<Text>
Aturan draf menyatakan bahwa jika lawan menawarkan bagian untuk
menangkap, itu harus diambil. &quot;gerakan paksa&quot; dapat
dipekerjakan untuk keuntungan besar. Jika draf lawan menghalangi Anda
cara membuat raja, Anda dapat memajukan bagian lain ke sisi lain
dari pemblokir yang menyinggung. Ini akan memaksa lawan Anda untuk menangkap
memungkinkan jalur yang jelas ke garis belakang untuk penobatan.
</Text>
<Text>
Memblokir digunakan untuk menggagalkan gerakan oposisi. Itu membutuhkan yang baik
banyak pemikiran ke depan oleh pemain lawan. Mencoba untuk yang kedua
menebak rencana atau rangkaian gerakan membutuhkan pengetahuan yang baik tentang
strategi. Sementara pemblokiran bersifat defensif dalam tujuannya (untuk mencegah
pemain lain dari maju) itu dapat menghasilkan posisi menang. Aku jatuh
potongan lawan diblokir dan dia tidak bisa bergerak, sesuai
dengan aturan permainan, dia kalah dalam permainan.
</Text>
{/*<Text>Here&apos;s a great video on draughts strategies:</Text>*/}
{/*<AspectRatio ratio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/Lfo3yfrbUs0"
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</AspectRatio>*/}
</VStack>
</MainLayout>
);
}

6
prettier.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

9
public/browserconfig.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffc40d</TileColor>
</tile>
</msapplication>
</browserconfig>

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
public/mstile-144x144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
public/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
public/mstile-310x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
public/mstile-310x310.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/mstile-70x70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3305 6993 c-605 -32 -1225 -238 -1730 -574 -223 -149 -354 -256
-545 -448 -194 -194 -301 -325 -449 -546 -299 -450 -491 -977 -557 -1535 -24
-198 -24 -582 0 -780 79 -671 332 -1277 752 -1805 113 -141 388 -416 529 -529
772 -615 1736 -877 2708 -735 604 87 1196 346 1682 735 142 114 417 388 529
529 419 526 672 1134 752 1805 24 198 24 582 0 780 -47 400 -154 771 -322
1120 -182 378 -385 663 -683 960 -287 286 -546 476 -891 649 -554 280 -1156
407 -1775 374z m635 -1984 c239 -419 440 -768 445 -775 8 -12 162 31 870 239
512 151 861 249 862 243 2 -6 -174 -546 -391 -1201 l-393 -1190 -1833 0 -1833
0 -393 1190 c-217 655 -393 1195 -391 1201 1 7 333 -86 862 -242 702 -207 862
-251 870 -240 6 7 206 356 445 775 239 418 437 761 440 761 3 0 201 -343 440
-761z m1290 -2987 c0 -5 -59 -187 -130 -405 l-131 -397 -1469 0 -1469 0 -131
396 c-71 218 -130 400 -130 405 0 5 744 9 1730 9 952 0 1730 -4 1730 -8z"/>
<path d="M3413 4099 c-89 -44 -176 -171 -222 -324 -141 -469 31 -1060 309
-1060 315 0 476 732 260 1182 -88 185 -223 263 -347 202z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

19
public/site.webmanifest Normal file
View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}