Skip to content

Commit

Permalink
feat(tictactoe): add tictactoe game
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerd Müller committed Jun 21, 2020
1 parent 096c7be commit a2d4f56
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions libs/data/src/lib/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum ContentType {
SmokingPit = "smokingpit",
Puzzle = "puzzle",
Suitcase = "suitcase",
TicTacToe = "tictactoe",
Decission = "decission"
}

Expand Down
1 change: 1 addition & 0 deletions libs/ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/tictactoe/tictactoe';
export * from './lib/suitcase/suitcase';
export * from './lib/header/header';
export * from './lib/group/group';
Expand Down
2 changes: 2 additions & 0 deletions libs/ui/src/lib/group/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Decission from '../decission/decission';
import Smokingpit from '../smokingpit/smokingpit';
import Puzzle from '../puzzle/puzzle';
import Suitcase from '../suitcase/suitcase';
import TicTacToe from '../tictactoe/tictactoe';
import Redirect from '../redirect/redirect';
import Info from '../info/info';

Expand Down Expand Up @@ -57,6 +58,7 @@ export const Group = ({
{content.type === ContentType.SmokingPit && <Smokingpit {...content} />}
{content.type === ContentType.Puzzle && <Puzzle/>}
{content.type === ContentType.Suitcase && <Suitcase/>}
{content.type === ContentType.TicTacToe && <TicTacToe/>}
</Fragment>
}

Expand Down
64 changes: 64 additions & 0 deletions libs/ui/src/lib/tictactoe/tictactoe.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
$color-bg: rgba(240, 230, 220, .3);
$color-border: #555;

.game {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 80%;

.status {
flex: 1 100%;
text-align: center;
margin: 10px 0;
}

.board {
display: flex;
flex-wrap: wrap;
width: 300px;
height: 300px;

.square {
width: 100px;
height: 100px;
border: 3px solid $color-border;

font-size: 32px;
text-align: center;
line-height: 100px;
cursor: pointer;
transition: background-color 0.3s;

&:focus {
outline: unset;
}

&:hover {
background-color: $color-bg;
}

&:nth-child(-n + 3) {
border-top: unset;
}

&:nth-last-child(-n + 3) {
border-bottom: unset;
}

&:nth-child(3n) {
border-right: unset;
}

&:nth-child(3n + 1) {
border-left: unset;
}

}
}

.result {
flex: 1 100%;
}
}
11 changes: 11 additions & 0 deletions libs/ui/src/lib/tictactoe/tictactoe.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { render } from '@testing-library/react';

import Tictactoe from './tictactoe';

describe(' Tictactoe', () => {
it('should render successfully', () => {
const { baseElement } = render(<Tictactoe />);
expect(baseElement).toBeTruthy();
});
});
120 changes: 120 additions & 0 deletions libs/ui/src/lib/tictactoe/tictactoe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, { useState, useContext } from "react";

import {AppContext } from '../chapter/context';

import './tictactoe.scss';

/* eslint-disable-next-line */
export interface TicTacToeProps {}

const Square = ({ value, onClick }) => {
return (
<div role="button" tabIndex={0} className='square' onClick={onClick}>{value}</div>
);
}

const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];

const userSymbol = "X";
const computerSymbol = "O";

export const TicTacToe = (props: TicTacToeProps) => {

const [character, setCharacter] = useContext(AppContext);
const status = `Um zu spielen, setze dein Kreuz in eines der Kästchen, indem du dort hinein klickst. Danach ist ${character} dran. Um zu gewinnen, musst du drei Kreuze in einer waagerechten, senkrechten oder diagonalen Reihe platzieren.`;

const [squares, setSquares] = useState(Array(9).fill(null));
const winner = calculateWinner(squares);

function getStatus() {
if (winner) {
document.querySelector('.result').scrollIntoView({ behavior: 'smooth' });
return winner === "X" ? "Du hast gewonnen!" : "Es ist ja nur ein Spiel!";
} else if (isBoardFull(squares)) {
document.querySelector('.result').scrollIntoView({ behavior: 'smooth' });
return "Ein solidarisches Remis!";
} else {
return '';
}
}

function renderSquare(i:number) {
return (
<Square
value={squares[i]}
key={i}
onClick={() => {
if (squares[i] != null || winner != null) {
return;
}
const nextSquares = squares.slice();
nextSquares[i] = userSymbol;
setSquares(nextSquares);

if (!calculateWinner(nextSquares)) {
setTimeout(() => {
const generatedSquares = nextSquares.slice();
const generatedSquare = setGeneratedSquare(generatedSquares);
if (generatedSquare != null) {
generatedSquares[generatedSquare] = computerSymbol;
setSquares(generatedSquares);
}
}, 300);
}
}}
/>
);
}

return (
<div className='game'>
<div className='status'>{status}</div>
<div className='board'>
{squares.map((value, index) => (
renderSquare(index)
))}
</div>
<div className='result'>{getStatus()}</div>
</div>
);
}

function setGeneratedSquare(squares) {
for (let i = 0; i < squares.length; i++) {
if (squares[i] == null) {
return i;
}
}
return null;
}

function calculateWinner(squares) {
// go over all possibly winning lines and check if they consist of only X's/only O's
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}

function isBoardFull(squares) {
for (let i = 0; i < squares.length; i++) {
if (squares[i] == null) {
return false;
}
}
return true;
}

export default TicTacToe;

0 comments on commit a2d4f56

Please sign in to comment.