From 7910a2746c5237884ffc46c2fc89d44fa72e7cec Mon Sep 17 00:00:00 2001 From: tatehito <48908346+Tatehito@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:46:46 +0900 Subject: [PATCH] feat: Add turtle graphics component (#12) --- src/app/lib/Character.ts | 146 +++++++++++++++++++ src/components/organisms/TurtleGraphics.tsx | 154 ++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 src/app/lib/Character.ts create mode 100644 src/components/organisms/TurtleGraphics.tsx diff --git a/src/app/lib/Character.ts b/src/app/lib/Character.ts new file mode 100644 index 00000000..6ad15482 --- /dev/null +++ b/src/app/lib/Character.ts @@ -0,0 +1,146 @@ +export class Character { + id: number; + name: string; + x: number; + y: number; + direction: string; + color: string; + penDown: boolean; + path: string[]; + + constructor( + id: number, + name: string, + x: number, + y: number, + direction: string, + color: string, + penDown: boolean, + path: string[] + ) { + this.id = id; + this.name = name; + this.x = x; + this.y = y; + this.direction = direction; + this.color = color; + this.penDown = penDown; + this.path = path; + } + + moveForward(gridColumns: number, gridRows: number): void { + switch (this.direction) { + case 'up': { + if (this.y <= 1) return; + + this.y -= 1; + break; + } + case 'down': { + if (this.y >= gridRows) return; + + this.y += 1; + break; + } + case 'left': { + if (this.x <= 1) return; + + this.x -= 1; + break; + } + case 'right': { + if (this.x >= gridColumns) return; + + this.x += 1; + break; + } + } + + if (this.penDown) { + this.path.push(`${this.x},${this.y}`); + } + } + + moveBack(gridColumns: number, gridRows: number): void { + switch (this.direction) { + case 'up': { + if (this.y >= gridRows) return; + + this.y += 1; + break; + } + case 'down': { + if (this.y <= 1) return; + + this.y -= 1; + break; + } + case 'left': { + if (this.x >= gridColumns) return; + + this.x += 1; + break; + } + case 'right': { + if (this.x <= 1) return; + + this.x -= 1; + break; + } + } + + if (this.penDown) { + this.path.push(`${this.x},${this.y}`); + } + } + + turnLeft(): void { + switch (this.direction) { + case 'up': { + this.direction = 'left'; + break; + } + case 'down': { + this.direction = 'right'; + break; + } + case 'left': { + this.direction = 'down'; + break; + } + case 'right': { + this.direction = 'up'; + break; + } + } + } + + turnRight(): void { + switch (this.direction) { + case 'up': { + this.direction = 'right'; + break; + } + case 'down': { + this.direction = 'left'; + break; + } + case 'left': { + this.direction = 'up'; + break; + } + case 'right': { + this.direction = 'down'; + break; + } + } + } + + putPen(): void { + this.penDown = true; + } + + upPen(): void { + this.penDown = false; + } +} diff --git a/src/components/organisms/TurtleGraphics.tsx b/src/components/organisms/TurtleGraphics.tsx new file mode 100644 index 00000000..8e777533 --- /dev/null +++ b/src/components/organisms/TurtleGraphics.tsx @@ -0,0 +1,154 @@ +'use client'; + +import { Box, Grid, GridItem } from '@chakra-ui/react'; +import React, { useState, useEffect } from 'react'; + +import { Character } from '../../app/lib/Character'; + +// 原点(左上隅)の座標 +const ORIGIN_X = 1; +const ORIGIN_Y = 1; + +interface TurtleGraphicsProps { + characters: Character[]; + gridColumns?: number; + gridRows?: number; + gridSize?: number; +} + +export const TurtleGraphics: React.FC = ({ + characters: initialCharacters, + gridColumns: gridColumns = 12, + gridRows: gridRows = 8, + gridSize: gridSize = 40, +}) => { + const [characters, setCharacters] = useState(initialCharacters); + + useEffect(() => { + // 軌跡の描画 + for (const character of characters) { + if (!character.penDown) continue; + + for (const position of character.path) { + const [x, y] = position.split(',').map(Number); + const gridCell = document.querySelectorAll('.grid-cell')[ + x - ORIGIN_X + (y - ORIGIN_Y) * gridColumns + ] as HTMLElement; + + if (!gridCell) return; + + gridCell.style.backgroundColor = character.color; + gridCell.style.opacity = '0.5'; + } + } + }, [characters, gridColumns]); + + const updateCharacter = (character: Character, updater: (char: Character) => void): void => { + setCharacters((prevCharacters) => + prevCharacters.map((prevCharacter) => { + if (prevCharacter.id === character.id) { + const updatedCharacter = new Character( + character.id, + character.name, + character.x, + character.y, + character.direction, + character.color, + character.penDown, + [...character.path] + ); + updater(updatedCharacter); + return updatedCharacter; + } + return prevCharacter; + }) + ); + }; + + const handleMoveForward = (character: Character): void => { + updateCharacter(character, (updatedCharacter) => { + updatedCharacter.moveForward(gridColumns, gridRows); + }); + }; + + const handleMoveBack = (character: Character): void => { + updateCharacter(character, (updatedCharacter) => { + updatedCharacter.moveBack(gridColumns, gridRows); + }); + }; + + const handleTurnleft = (character: Character): void => { + updateCharacter(character, (updatedCharacter) => { + updatedCharacter.turnLeft(); + }); + }; + + const handleTurnRight = (character: Character): void => { + updateCharacter(character, (updatedCharacter) => { + updatedCharacter.turnRight(); + }); + }; + + const handlePutPen = (character: Character): void => { + updateCharacter(character, (updatedCharacter) => { + updatedCharacter.putPen(); + }); + }; + + const handleUpPen = (character: Character): void => { + updateCharacter(character, (updatedCharacter) => { + updatedCharacter.upPen(); + }); + }; + + return ( +
+ + {Array.from({ length: gridColumns * gridRows }).map((_, index) => ( + + ))} + {characters.map((character) => ( + + {character.name} + + ))} + + {characters.map((character) => ( +
+
{character.name}
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
---
+
+ ))} +
+ ); +};