This commit is contained in:
bakhirev 2024-07-09 23:33:55 +03:00
parent ccc41b7dc3
commit a8475a030f
35 changed files with 94 additions and 521 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,33 +0,0 @@
import React from 'react';
import style from '../style/map.module.scss';
import { getRandom } from '../helpers/level';
interface IBlockProps {
type?: string;
}
function Block({
type,
}: IBlockProps): React.ReactElement | null {
const className = [style.city_builder_block];
const defaultSprite = './assets/games/road.png';
const src = {
home: `./assets/games/home${getRandom(2)}.png`,
road: './assets/games/road.png',
}[type || ''] || defaultSprite;
if (type === 'green') {
return (<div className={className.join(' ')} />);
}
return (
<img
className={className.join(' ')}
src={src}
/>
);
}
export default Block;

View file

@ -1,35 +0,0 @@
import React from 'react';
import Block from './Block';
interface IBlocksProps {
level: any;
}
function Blocks({
level,
}: IBlocksProps): React.ReactElement | null {
const blocks: any[] = [];
const lastRowIndex = level?.length - 1;
const lastColumnIndex = level?.[0]?.length - 1;
level?.forEach((row: any, rowIndex: number) => {
row.forEach((cell: any, columnIndex: number) => {
let type = 'home';
if (cell) type = 'road';
if ((!rowIndex || !columnIndex || rowIndex === lastRowIndex || columnIndex === lastColumnIndex)
&& cell
&& Math.random() > 0.5) type = 'green';
blocks.push(
<Block
key={`${rowIndex}.${columnIndex}`}
type={type}
/>);
});
});
return (<>{blocks}</>);
}
export default Blocks;

View file

@ -1,54 +0,0 @@
import React, { useEffect, useState } from 'react';
import { getRandomLevel } from '../helpers/level';
import Blocks from './Blocks';
import style from '../style/map.module.scss';
function getCitySize(percent: number): [number, number] {
if (percent > 70) return [20, 20];
if (percent > 60) return [18, 18];
if (percent > 50) return [16, 16];
if (percent > 40) return [14, 14];
if (percent > 20) return [12, 12];
if (percent > 10) return [10, 10];
return [8, 8];
}
interface ICityMapProps {
percent: number;
}
function CityMap({
percent,
}: ICityMapProps): React.ReactElement | null {
const size = getCitySize(percent || 5);
const defaultLevel = getRandomLevel(...size);
const [level, setLevel] = useState<any>(defaultLevel);
useEffect(() => {
const newSize = getCitySize(percent || 5);
const newLevel = getRandomLevel(...newSize);
setLevel(newLevel);
}, [percent]);
const cellSize = 20;
const paddingTop = (24 - level.length) / 2;
const paddingLeft = (24 - level[0].length) / 2;
return (
<div
className={style.city_builder_wrapper}
style={{
padding: `${paddingTop * cellSize}px ${paddingLeft * cellSize}px`,
maxWidth: 24 * cellSize,
}}
>
<div className={style.city_builder}>
<Blocks level={level} />
</div>
</div>
);
}
export default CityMap;

View file

@ -1,79 +0,0 @@
type IMap = number[][];
type ICell = {
row: number,
column: number,
corridor: [number, number],
};
function getMap(height: number, width: number): IMap {
return Array(height).fill(0).map(() => Array(width).fill(0));
}
export function getRandom(max: number): number {
return Math.round(Math.random() * max);
}
function getNeighbor(visited: any, row: number, column: number): ICell | null {
const neighbors: ICell[] = [];
if ((visited[row - 2] || [])[column] === 0) neighbors.push({
row: row - 2,
column: column,
corridor: [row - 1, column],
});
if (visited[row][column + 2] === 0) neighbors.push({
row: row,
column: column + 2,
corridor: [row, column + 1],
});
if (visited[row][column - 2] === 0) neighbors.push({
row: row,
column: column - 2,
corridor: [row, column - 1],
});
if ((visited[row + 2] || [])[column] === 0) neighbors.push({
row: row + 2,
column: column,
corridor: [row + 1, column],
});
if (!neighbors.length) return null;
const index = getRandom(neighbors.length - 1);
return neighbors[index];
}
function step(visited: IMap, history: any) {
if (history.length === 0) return false;
const current = history[history.length - 1];
visited[current.row][current.column] = 1;
const neighbor = getNeighbor(visited, current.row, current.column);
if (neighbor) {
visited[neighbor.corridor[0]][neighbor.corridor[1]] = 1;
history.push(neighbor);
} else {
history.pop();
}
return true;
}
export function getRandomLevel( width: number, height: number) {
const visited = getMap(height + 2, width + 2);
const stack = [{ row: 1, column: 1 }];
while (step(visited, stack)) {
}
visited.shift();
visited.forEach((row) => {
row.shift();
});
return visited;
}
export function getCopyLevel(level: IMap) {
return level.map(row => [...row]);
}
export function printLevel(level: IMap) {
console.log(level.map(row => row.map(v => v ? '#' : ' ').join(' ')).join('\n'));
}

View file

@ -1,86 +0,0 @@
import React, { useEffect, useState } from 'react';
import IHashMap from 'ts/interfaces/HashMap';
import Description from 'ts/components/Description';
import ShowSymbol from 'ts/components/ShowSymbol';
import { shuffle } from 'ts/helpers/random';
import GameBanner from 'ts/components/GameBanner';
import styleBanner from 'ts/components/GameBanner/index.module.scss';
import CityMap from './components/CityMap';
import style from './style/wrapper.module.scss';
interface IValue {
title: string;
value: number;
}
function getTotal(valuesByKey: IHashMap<number>): number {
return Object.values(valuesByKey || {})
.reduce((sum: number, value: number) => sum + value, 0);
}
function getRandomList(valuesByKey: IHashMap<number>): IValue[] {
const list = Object.entries(valuesByKey || {})
.map(([title, value]: any) => ({ title, value }));
return shuffle(list);
}
interface ICityBuilderProps {
valuesByTitle: IHashMap<number>;
}
function CityBuilder({
valuesByTitle,
}: ICityBuilderProps): React.ReactElement | null {
const [list, setList] = useState<IValue[]>([]);
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const [total, setTotal] = useState<number>(100);
const lastIndex = list.length - 1;
useEffect(() => {
setTotal(getTotal(valuesByTitle));
setList(getRandomList(valuesByTitle));
}, [valuesByTitle]);
if (!list.length) return null;
const selected = list[selectedIndex];
const percent = Math.ceil(((selected.value || 0) * 100) / total);
return (
<>
<GameBanner src="./assets/games/citybuilder.jpg">
<ShowSymbol
text={selected.title}
length={20}
/>
<Description
className={styleBanner.game_banner_text}
text={`Сейчас в проекте есть ${selected.value || 0} файлов созданных этим пользователем. Это примерно ${percent}% от всех файлов в проекте.`}
/>
</GameBanner>
<div className={style.city_builder_control}>
<button
disabled={!selectedIndex}
className={style.city_builder_control_prev}
style={{ backgroundImage: 'url(./assets/menu/arrow_left.svg)' }}
onClick={() => {
setSelectedIndex(selectedIndex - 1);
}}
/>
<button
disabled={selectedIndex === lastIndex}
className={style.city_builder_control_next}
style={{ backgroundImage: 'url(./assets/menu/arrow_right.svg)' }}
onClick={() => {
setSelectedIndex(selectedIndex + 1);
}}
/>
<CityMap percent={percent} />
</div>
</>
);
}
export default CityBuilder;

View file

@ -1,36 +0,0 @@
@import 'src/styles/variables';
.city_builder_banner {
position: relative;
display: block;
width: 100%;
height: 300px;
margin: var(--space-xxl) auto 0;
user-select: none;
background-size: auto 100%;
background-repeat: repeat;
background-position: top left;
&_description {
position: absolute;
left: 0;
right: 0;
display: block;
width: 100%;
padding: 0 var(--space-xxl);
}
&_description {
bottom: 0;
padding-top: var(--space-xxl);
background-color: rgba(0, 0, 0, 0.7);
}
&_text {
margin: var(--space-s) auto;
color: var(--color-white);
}
}

View file

@ -1,40 +0,0 @@
@import 'src/styles/variables';
.city_builder {
--temp-min-size: 20px;
display: block;
margin: 0;
padding: 0;
vertical-align: top;
&_wrapper {
max-width: 480px;
padding: 40px;
margin: 0 auto;
box-sizing: border-box;
}
&_block {
display: inline-block;
width: var(--temp-min-size);
height: var(--temp-min-size);
margin: 0;
padding: 0;
vertical-align: top;
text-decoration: none;
box-sizing: border-box;
border: none;
background-repeat: repeat;
background-size: auto 100%;
background-position: center center;
&_home {
background-repeat: repeat;
}
}
}

View file

@ -1,50 +0,0 @@
@import 'src/styles/variables';
.city_builder_description {
margin: var(--space-m) auto;
}
.city_builder_control {
position: relative;
margin: 0 auto var(--space-xxl);
user-select: none;
background-color: var(--color-13);
background-size: auto;
background-repeat: repeat;
background-position: top left;
&_prev,
&_next {
position: absolute;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
width: 30%;
height: 100%;
cursor: pointer;
border-radius: 0;
border: none;
color: transparent;
background-color: transparent;
background-size: auto 30%;
background-repeat: no-repeat;
background-position: center center;
}
&_prev {
left: 0;
right: 30%;
}
&_next {
left: 70%;
right: 100%;
}
}

View file

@ -17,25 +17,25 @@ function Answer({
onClick,
}: IAnswerProps): React.ReactElement | null {
const [iconIndex] = useState(getRandom(5));
const className = [style.quize_answer_wrapper];
const textClasName = [style.quize_answer_text];
const className = [style.quiz_answer_wrapper];
const textClasName = [style.quiz_answer_text];
if (mode === 'small' || mode === 'error') className.push(style.quize_answer_wrapper_small);
if (mode === 'selected') textClasName.push(style.quize_answer_text_selected);
if (mode === 'correct') textClasName.push(style.quize_answer_text_correct);
if (mode === 'error') textClasName.push(style.quize_answer_text_error);
if (mode === 'small' || mode === 'error') className.push(style.quiz_answer_wrapper_small);
if (mode === 'selected') textClasName.push(style.quiz_answer_text_selected);
if (mode === 'correct') textClasName.push(style.quiz_answer_text_correct);
if (mode === 'error') textClasName.push(style.quiz_answer_text_error);
return (
<div className={className.join(' ')}>
<figure
className={style.quize_answer}
className={style.quiz_answer}
onClick={() => {
onClick();
}}
>
<img
className={style.quize_answer_icon}
src={`./assets/games/quize/balloon_${iconIndex}.png`}
className={style.quiz_answer_icon}
src={`./assets/games/quiz/balloon_${iconIndex}.png`}
/>
<figcaption className={textClasName.join(' ')}>
{answer.title}

View file

@ -10,9 +10,9 @@ function Progress({
progress,
}: IProgressProps): React.ReactElement | null {
return (
<div className={style.quize_progress}>
<div className={style.quize_progress_line}>
<div className={style.quize_progress_text}>
<div className={style.quiz_progress}>
<div className={style.quiz_progress_line}>
<div className={style.quiz_progress_text}>
{progress}
</div>
</div>

View file

@ -67,18 +67,18 @@ function Question({
));
return (
<div className={stylePage.quize_question}>
<div className={stylePage.quize_question_body}>
<div className={stylePage.quiz_question}>
<div className={stylePage.quiz_question_body}>
<div
className={style.quize_title}
style={{ backgroundImage: 'url(./assets/games/quize/cloud_big.png)' }}
className={style.quiz_title}
style={{ backgroundImage: 'url(./assets/games/quiz/cloud_big.png)' }}
>
{question.title}
</div>
<div className={style.quize_question_answer}>
<div className={style.quiz_question_answer}>
{answers}
</div>
<div className={style.quize_footer}>
<div className={style.quiz_footer}>
<UiKitButton
disabled={disabled}
onClick={() => {

View file

@ -17,14 +17,14 @@ function Result({
onClick,
}: IResultProps): React.ReactElement | null {
return (
<section className={stylePage.quize_result}>
<h4 className={style.quize_title}>
<section className={stylePage.quiz_result}>
<h4 className={style.quiz_title}>
{result.title}
</h4>
<p className={style.quize_description}>
<p className={style.quiz_description}>
{result.description}
</p>
<div className={style.quize_footer}>
<div className={style.quiz_footer}>
<UiKitButton
onClick={() => {
onClick();

View file

@ -2,35 +2,35 @@ import React from 'react';
import UiKitButton from 'ts/components/UiKit/components/Button';
import IQuize from '../interfaces/Quize';
import IQuiz from '../interfaces/Quiz';
import stylePage from '../styles/start.module.scss';
import style from '../styles/index.module.scss';
interface IStartProps {
quize: IQuize;
quiz: IQuiz;
onClick: Function;
}
function Start({
quize,
quiz,
onClick,
}: IStartProps): React.ReactElement | null {
return (
<section className={stylePage.quize_start}>
<h4 className={style.quize_title}>
{quize.title}
<section className={stylePage.quiz_start}>
<h4 className={style.quiz_title}>
{quiz.title}
</h4>
<p className={style.quize_description}>
{quize.description}
<p className={style.quiz_description}>
{quiz.description}
</p>
<div className={style.quize_footer}>
<div className={style.quiz_footer}>
<UiKitButton
onClick={() => {
onClick();
}}
>
{quize.button || 'GO'}
{quiz.button || 'GO'}
</UiKitButton>
</div>
</section>

View file

@ -4,7 +4,7 @@ import Result from './Result';
import Question from './Question';
import Start from './Start';
import IQuize from '../interfaces/Quize';
import IQuiz from '../interfaces/Quiz';
import IQuestion from '../interfaces/Question';
import IAnswer from '../interfaces/Answer';
import IResult from '../interfaces/Result';
@ -25,29 +25,29 @@ function getApplyInAnimation(setShowSlide: Function, delay: number) {
};
}
interface IQuizePageProps {
quize: IQuize;
interface IQuizPageProps {
quiz: IQuiz;
onEnd: Function;
}
function QuizePage({
quize,
function QuizPage({
quiz,
onEnd,
}: IQuizePageProps): React.ReactElement | null {
const [question, setQuestion] = useState<IQuestion>(quize.questions[0]);
const [result, setResult] = useState<IResult>(quize.results[0]);
}: IQuizPageProps): React.ReactElement | null {
const [question, setQuestion] = useState<IQuestion>(quiz.questions[0]);
const [result, setResult] = useState<IResult>(quiz.results[0]);
const [answers, setAnswers] = useState<IAnswer[]>([]);
const [view, setView] = useState<string>('start');
const [showSlide, setShowSlide] = useState<boolean>(false);
const applyInAnimation = getApplyInAnimation(setShowSlide, 1500);
const questions = getQuestionByGroups(quize.questions);
const questions = getQuestionByGroups(quiz.questions);
let page: any = null;
if (view === 'start') {
page = (
<Start
quize={quize}
quiz={quiz}
onClick={() => {
applyInAnimation(() => {
setView('question');
@ -64,7 +64,7 @@ function QuizePage({
onClick={(answer: IAnswer) => {
const nextById = questions.byId[answer.nextQuestionId || ''];
const nextByIndex = questions.byIndex[question.index + 1];
const newResult = getResult(answers, quize.results);
const newResult = getResult(answers, quiz.results);
setAnswers([...answers, answer]);
if (answer.isEnd) {
@ -98,7 +98,7 @@ function QuizePage({
onClick={() => {
applyInAnimation(() => {
onEnd();
setQuestion(quize.questions[0]);
setQuestion(quiz.questions[0]);
setAnswers([]);
setView('start');
});
@ -108,21 +108,21 @@ function QuizePage({
}
const className = showSlide
? `${style.quize_slider} ${style.quize_slider_animation}`
: style.quize_slider;
? `${style.quiz_slider} ${style.quiz_slider_animation}`
: style.quiz_slider;
return (
<div
className={style.quize_container}
style={{ backgroundImage: 'url(./assets/games/quize/cloud_bg.png)' }}
className={style.quiz_container}
style={{ backgroundImage: 'url(./assets/games/quiz/cloud_bg.png)' }}
>
<div
className={style.quize_cloud_bg}
style={{ backgroundImage: 'url(./assets/games/quize/cloud_bg.png)' }}
className={style.quiz_cloud_bg}
style={{ backgroundImage: 'url(./assets/games/quiz/cloud_bg.png)' }}
/>
<div
className={style.quize_cloud}
style={{ backgroundImage: 'url(./assets/games/quize/cloud.png)' }}
className={style.quiz_cloud}
style={{ backgroundImage: 'url(./assets/games/quiz/cloud.png)' }}
/>
<div className={className}>
{page}
@ -131,4 +131,4 @@ function QuizePage({
);
}
export default QuizePage;
export default QuizPage;

View file

@ -0,0 +1,20 @@
import React from 'react';
import IQuiz from './interfaces/Quiz';
import QuizPage from './components/index';
import example from './helpers/example';
interface IQuizProps {
}
function Quiz({}: IQuizProps): React.ReactElement | null {
return (
<QuizPage
quiz={example as IQuiz}
onEnd={() => {
}}
/>
);
}
export default Quiz;

View file

@ -1,7 +1,7 @@
import IQuestion from './Question';
import IResult from './Result';
export default interface IQuize {
export default interface IQuiz {
id?: number;
icon?: string;
title?: string;

View file

@ -1,6 +1,6 @@
@import 'src/styles/variables';
.quize_answer {
.quiz_answer {
--temp-width: 160px;
--temp-small: calc(var(--temp-width) - 32px);
@ -97,30 +97,30 @@
}
}
.quize_answer_wrapper + .quize_answer_wrapper {
.quiz_answer_wrapper + .quiz_answer_wrapper {
margin-left: 64px;
}
.quize_answer_wrapper_small {
.quiz_answer_wrapper_small {
padding: var(--space-s) var(--space-l);
& .quize_answer {
& .quiz_answer {
width: var(--temp-small);
animation-delay: 1s;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-fill-mode: both;
animation-name: quize_answer;
animation-name: quiz_answer;
}
& .quize_answer_text {
& .quiz_answer_text {
font-size: var(--font-s);
}
}
@keyframes quize_answer {
@keyframes quiz_answer {
from {
top: 0;
}

View file

@ -1,6 +1,6 @@
@import 'src/styles/variables';
.quize {
.quiz {
&_container {
position: relative;
display: block;
@ -33,7 +33,7 @@
background-size: auto 100%;
background-repeat: repeat;
animation-name: quize_cloud;
animation-name: quiz_cloud;
animation-iteration-count: infinite;
animation-fill-mode: none;
}
@ -57,7 +57,7 @@
animation-fill-mode: both;
&_animation {
animation-name: quize_slider;
animation-name: quiz_slider;
}
}
@ -124,7 +124,7 @@
}
}
@keyframes quize_slider {
@keyframes quiz_slider {
from {
bottom: 0;
left: auto;
@ -145,7 +145,7 @@
}
}
@keyframes quize_cloud {
@keyframes quiz_cloud {
from {
background-position: 0 0;
}

View file

@ -1,6 +1,6 @@
@import 'src/styles/variables';
.quize_progress {
.quiz_progress {
&_description {
}
}

View file

@ -1,6 +1,6 @@
@import 'src/styles/variables';
.quize_question {
.quiz_question {
&_title {
font-size: var(--font-l);
font-weight: 100;

View file

@ -1,6 +1,6 @@
@import 'src/styles/variables';
.quize_result {
.quiz_result {
&_title,
&_description {
font-weight: 100;

View file

@ -1,4 +1,4 @@
@import 'src/styles/variables';
.quize_start {
.quiz_start {
}

View file

@ -1,20 +0,0 @@
import React from 'react';
import IQuize from './interfaces/Quize';
import QuizePage from './components/index';
import example from './helpers/example';
interface IQuizeProps {
}
function Quize({}: IQuizeProps): React.ReactElement | null {
return (
<QuizePage
quize={example as IQuize}
onEnd={() => {
}}
/>
);
}
export default Quize;

View file

@ -5,19 +5,11 @@ import dataGripStore from 'ts/store/DataGrip';
import Title from 'ts/components/Title';
import Races from 'ts/components/Races';
import CityBuilder from 'ts/components/CityBuilder';
import SwimmingPool from 'ts/components/SwimmingPool';
import Quize from 'ts/components/Quize';
import Quiz from 'ts/components/Quiz';
const TeamBuilding = observer((): React.ReactElement => {
const filesByAuthor = dataGripStore.fileGrip.author?.statisticByName || {};
const addedFilesByAuthor = Object.entries(filesByAuthor)
.reduce((acc: any, item: any) => {
acc[item[0]] = item[1].addedFiles;
return acc;
}, {});
const tracksAuth = dataGripStore.dataGrip.author.statistic
.filter((item: any) => !item.isStaff);
const value = tracksAuth.map((statistic: any) => statistic.taskInDay);
@ -33,16 +25,10 @@ const TeamBuilding = observer((): React.ReactElement => {
return (
<>
<Quize />
{/*<Title title="Скорость закрытия задач"/>*/}
<Quiz />
<Races tracks={tracks} />
<Title title="Максимальная длинна подписи коммита"/>
<SwimmingPool tracks={maxMessageLength}/>
<Title title="Количество созданных файлов, если бы это был город"/>
<CityBuilder valuesByTitle={addedFilesByAuthor} />
<Title title="Скорость коммитов в день"/>
{'Небоскребы вверх ввиде графика'}
</>
);
});

View file

@ -18,7 +18,7 @@ import Type from './components/Type';
import Week from './components/Week';
import Month from './components/Month';
import Tasks from './components/Tasks';
import TeamBuilding from './components/Top';
import Building from './components/Building';
import Pr from './components/PR';
import Print from './components/Print';
import Release from './components/Release';
@ -41,7 +41,7 @@ function getViewById(page?: string) {
if (page === 'commits') return <Commits/>;
if (page === 'changes') return <Changes/>;
if (page === 'words') return <PopularWords mode={mode}/>;
if (page === 'building') return <TeamBuilding/>;
if (page === 'building') return <Building/>;
if (page === 'print') return <Print/>;
if (page === 'tasks') return <Tasks/>;
return <Total/>;