Skip to content

Instantly share code, notes, and snippets.

@JoelCodes
Created December 14, 2019 02:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoelCodes/177a01ff21e6258fbf89ef67db2e026e to your computer and use it in GitHub Desktop.
Save JoelCodes/177a01ff21e6258fbf89ef67db2e026e to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link href="https://fonts.googleapis.com/css?family=Baskervville&display=swap" rel="stylesheet">
<style>
*{
font-family: 'Baskervville', serif;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="./index.jsx"></script>
</body>
</html>
import React, {useState} from 'react';
import {render} from 'react-dom';
import { TicTacToePresenter } from './presenter';
import { useTicTacToe, useTicTacToeFromReducer } from './useTicTacToe';
// class TicTacToeContainer extends React.Component{
// state = {}
// }
function TicTacToeContainer(){
return (<TicTacToePresenter {...useTicTacToeFromReducer()}/>)
}
render(<TicTacToeContainer/>, document.getElementById('root'));
{
"name": "mandy-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Joel Shinness <me@joelshinness.com> (http://joelshinness.com)",
"license": "ISC",
"dependencies": {
"parcel": "^1.12.4",
"react": "^16.12.0",
"react-dom": "^16.12.0"
}
}
import React, {useState, useEffect} from 'react';
const faces = ['πŸ˜€', 'πŸ˜ƒ', 'πŸ˜„', '😁', 'πŸ˜†', 'πŸ˜…', 'πŸ˜‚']
function Laugher(){
const [index, setIndex] = useState(0);
useEffect(() => {
const intervalId = setInterval(()=> {
setIndex((i) => (i + 1) % faces.length);
}, 500);
return () => {
clearInterval(intervalId)
}
}, []);
return <>{faces[index]}</>
}
export function TicTacToePresenter({grid, turn, makeTurn, clear, winner}){
return <div>
<h1>{winner ? <>{winner} Won!<Laugher/></> : `${turn}'s Turn.`}</h1>
<p>
<svg viewBox="-10 -10 320 320" width='500'>
<path d="M 0 100 H 300 M 0 200 H 300 M 100 0 V 300 M 200 0 V 300" stroke='black' fill='none' strokeWidth="10" strokeLinecap="round" />
{grid.map((mark, i) => {
const x = (i % 3) * 100;
const y = Math.floor( i / 3) * 100;
const transform = `translate(${x},${y})`;
if(mark === 'X'){
return <path d="M 10 10 L 90 90 M 10 90 L 90 10" stroke='red' fill='none' strokeWidth="5" strokeLinecap="round" transform={transform}/>
}
if(mark === 'O'){
return <circle cx="50" cy="50" r="40" stroke='blue' fill='none' strokeWidth="5" strokeLinecap="round" transform={transform}/>
}
return <rect fill='tomato' x="5" y="5" width="90" height="90" transform={transform} onClick={winner ? undefined : (() => {makeTurn(i); }) }/>
})}
</svg>
</p>
<p><button onClick={clear}>Clear</button></p>
</div>
}
export const initialState = {
grid: [...new Array(9)].fill(' '),
turn: 'X',
winner: undefined,
}
const winConditions = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[1,4,7],
[2,5,8],
[0,4,8],
[2,4,6]
];
function hasWin(grid){
for(const [a,b,c] of winConditions){
if(grid[a] !== ' ' && grid[a] === grid[b] && grid[b] === grid[c]){
return true;
}
}
return false;
}
function makeMove(gameState, i){
const {grid, turn, winner} = gameState
if(winner !== undefined){
return gameState;
}
const newGrid = [...grid];
newGrid[i] = turn;
return {
grid: newGrid,
winner: hasWin(newGrid) ? turn : undefined,
turn: turn === 'X' ? 'O' : 'X'
}
}
export function ticTacToeReducer(gameState, action){
switch(action.type){
case 'MAKE_MOVE': {
return makeMove(gameState, action.payload)
}
case 'CLEAR': {
return initialState;
}
default:{
return gameState;
}
}
}
import {useState, useReducer} from 'react';
import { ticTacToeReducer, initialState } from './ticTacToeReducer';
const winConditions = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[1,4,7],
[2,5,8],
[0,4,8],
[2,4,6]
];
function hasWin(grid){
for(const [a,b,c] of winConditions){
if(grid[a] !== ' ' && grid[a] === grid[b] && grid[b] === grid[c]){
return true;
}
}
return false;
}
const blankArray = [...new Array(9)].fill(' ');
export function useTicTacToe(){
const [turn, setTurn] = useState('X');
const [grid, setGrid] = useState(blankArray);
const [winner, setWinner] = useState(undefined)
const makeTurn = (i) => {
const newGrid = [...grid];
newGrid[i] = turn;
if(hasWin(newGrid)){
setWinner(turn);
}
setTurn(turn === 'X' ? 'O': 'X');
setGrid(newGrid);
}
const clear = () => {
setTurn('X');
setGrid(blankArray);
setWinner(undefined)
}
return {grid, winner, turn, clear, makeTurn};
}
export function useTicTacToeFromReducer(){
const [state, dispatch] = useReducer(ticTacToeReducer, {
grid: ['X', 'X', ' ', 'O', 'O', ' ', ' ', ' ', ' '],
winner: undefined,
turn: 'X'
})
function makeTurn(i){
dispatch({type: 'MAKE_MOVE', payload: i});
}
function clear(){
dispatch({type: 'CLEAR'});
}
return {...state, clear, makeTurn}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment