git init
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/.idea
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
17103
package-lock.json
generated
Normal file
17103
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "react.tictactoe",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "/src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test --env=jsdom",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"react-scripts": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
11835
pnpm-lock.yaml
generated
Normal file
11835
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
public/index.html
Normal file
11
public/index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
162
src/App.js
Normal file
162
src/App.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import {useMemo, useState} from "react";
|
||||||
|
|
||||||
|
const SQUARE_SIZE = 3;
|
||||||
|
|
||||||
|
function Square({ id, value, onSquareClick, winner, points }) {
|
||||||
|
return <button className="square" onClick={onSquareClick} style={{
|
||||||
|
color: winner && points?.includes(id) ? 'green' : 'black',
|
||||||
|
}}>{value}</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Board({ xIsNext, squares, onPlay }) {
|
||||||
|
const { winner, points } = calculateWinner(squares);
|
||||||
|
|
||||||
|
function handleClick(i) {
|
||||||
|
if (squares[i] || winner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextSquares = squares.slice();
|
||||||
|
|
||||||
|
if (xIsNext) {
|
||||||
|
nextSquares[i] = 'X';
|
||||||
|
} else {
|
||||||
|
nextSquares[i] = 'O';
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlay(nextSquares);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status;
|
||||||
|
|
||||||
|
const rendering = useMemo(() => {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < SQUARE_SIZE; i++) {
|
||||||
|
result.push(
|
||||||
|
<div key={i} className="board-row">
|
||||||
|
{Array.from({length: SQUARE_SIZE}).map((_, j) => (
|
||||||
|
<Square key={i * 3 + j}
|
||||||
|
id={i * 3 + j}
|
||||||
|
onSquareClick={() => handleClick(i * 3 + j)}
|
||||||
|
value={squares[i * 3 + j]}
|
||||||
|
winner={winner}
|
||||||
|
points={points}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [squares, winner, points]);
|
||||||
|
|
||||||
|
if (winner) {
|
||||||
|
status = '승자: ' + winner;
|
||||||
|
} else {
|
||||||
|
status = '다음 플레이어: ' + (xIsNext ? 'X' : 'O');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="status">{status}</div>
|
||||||
|
{ rendering }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Game() {
|
||||||
|
const [history, setHistory] = useState([Array(9).fill(null)]);
|
||||||
|
const [currentMove, setCurrentMove] = useState(0);
|
||||||
|
const currentSquares = history[currentMove];
|
||||||
|
const [isAscending, setIsAscending] = useState(true);
|
||||||
|
|
||||||
|
const xIsNext = currentMove % 2 === 0;
|
||||||
|
|
||||||
|
function handlePlay(nextSquares) {
|
||||||
|
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
|
||||||
|
setHistory(nextHistory);
|
||||||
|
setCurrentMove(nextHistory.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpTo(nextMove) {
|
||||||
|
setCurrentMove(nextMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moves = history.map((squares, move) => {
|
||||||
|
console.log('squares', squares);
|
||||||
|
|
||||||
|
let description;
|
||||||
|
|
||||||
|
const calculateUserPick = (move) => {
|
||||||
|
let answer = null;
|
||||||
|
const nowSquares = history[move];
|
||||||
|
const prevSquares = history[move - 1];
|
||||||
|
|
||||||
|
for (let i = 0; i < squares.length; i++) {
|
||||||
|
if (nowSquares[i] !== prevSquares[i]) {
|
||||||
|
answer = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = Math.floor(answer / SQUARE_SIZE);
|
||||||
|
const col = answer % SQUARE_SIZE;
|
||||||
|
return [row + 1, col + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move > 0) {
|
||||||
|
const [row, col] = calculateUserPick(move);
|
||||||
|
description = '# ' + move + '로 이동하기 (' + row + ', ' + col + ')';
|
||||||
|
} else {
|
||||||
|
description = '시작지점으로 이동하기';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={move}>
|
||||||
|
<button onClick={() => jumpTo(move)}>{description}</button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="game">
|
||||||
|
<div className="game-board">
|
||||||
|
<div>당신은 {currentMove + 1}번째 순서에 있습니다...</div>
|
||||||
|
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
|
||||||
|
</div>
|
||||||
|
<div className="game-info">
|
||||||
|
<ol><button onClick={() => setIsAscending(!isAscending)}>순서변경</button></ol>
|
||||||
|
<ol>{isAscending ? moves : moves.reverse() }</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
function calculateWinner(squares) {
|
||||||
|
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]
|
||||||
|
];
|
||||||
|
|
||||||
|
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 {
|
||||||
|
winner: squares[a],
|
||||||
|
points: lines[i],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
winner: null,
|
||||||
|
points: lines,
|
||||||
|
};
|
||||||
|
}
|
||||||
12
src/index.js
Normal file
12
src/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React, { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
const root = createRoot(document.getElementById("root"));
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
93
src/styles.css
Normal file
93
src/styles.css
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-inline-start: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #999;
|
||||||
|
float: left;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 34px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: -1px;
|
||||||
|
margin-top: -1px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-row:after {
|
||||||
|
clear: both;
|
||||||
|
content: '';
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.game {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user