Ryo4499 / stop-human-wave-tactics

2023 Publication of technical articles and results
https://datsujinkai.com
0 stars 0 forks source link

モールス信号練習機の実装 #154

Closed Ryo4499 closed 4 months ago

Ryo4499 commented 4 months ago
// pages/index.tsx
import React, { useState, useEffect, useCallback } from 'react';
import Head from 'next/head';
import { 
  ThemeProvider, 
  createTheme, 
  CssBaseline,
  Container,
  Typography,
  Button,
  Box,
  Paper
} from '@mui/material';
import RefreshIcon from '@mui/icons-material/Refresh';

const theme = createTheme({
  palette: {
    mode: 'light',
    primary: {
      main: '#4CAF50',
    },
    secondary: {
      main: '#f44336',
    },
  },
});

const morseCode: { [key: string]: string } = {
  '.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E',
  '..-.': 'F', '--.': 'G', '....': 'H', '..': 'I', '.---': 'J',
  '-.-': 'K', '.-..': 'L', '--': 'M', '-.': 'N', '---': 'O',
  '.--.': 'P', '--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T',
  '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X', '-.--': 'Y',
  '--..': 'Z', '.----': '1', '..---': '2', '...--': '3', '....-': '4',
  '.....': '5', '-....': '6', '--...': '7', '---..': '8', '----.': '9',
  '-----': '0'
};

const Home: React.FC = () => {
  const [morseInput, setMorseInput] = useState<string>('');
  const [currentLetter, setCurrentLetter] = useState<string>('');
  const [result, setResult] = useState<string>('');
  const [isPressed, setIsPressed] = useState<boolean>(false);
  const dotDuration = 250; // milliseconds

  const startPress = useCallback(() => {
    setIsPressed(true);
  }, []);

  const endPress = useCallback(() => {
    setIsPressed(false);
    const duration = new Date().getTime() - pressStartTime;
    setCurrentLetter(prev => prev + (duration < dotDuration ? '.' : '-'));
  }, []);

  const addLetter = useCallback(() => {
    if (currentLetter) {
      setMorseInput(prev => prev + currentLetter + ' ');
      setCurrentLetter('');
    }
  }, [currentLetter]);

  const updateResult = useCallback(() => {
    const decoded = morseInput
      .trim()
      .split(' ')
      .map(code => morseCode[code] || '?')
      .join('');
    setResult(decoded);
  }, [morseInput]);

  const resetInput = useCallback(() => {
    setMorseInput('');
    setCurrentLetter('');
    setResult('');
  }, []);

  useEffect(() => {
    updateResult();
  }, [morseInput, updateResult]);

  useEffect(() => {
    const timer = setTimeout(addLetter, dotDuration * 3);
    return () => clearTimeout(timer);
  }, [currentLetter, addLetter]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.code === 'Space' && !e.repeat) {
        e.preventDefault();
        startPress();
      }
    };

    const handleKeyUp = (e: KeyboardEvent) => {
      if (e.code === 'Space') {
        e.preventDefault();
        endPress();
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('keyup', handleKeyUp);

    return () => {
      window.removeEventListener('keydown', handleKeyDown);
      window.removeEventListener('keyup', handleKeyUp);
    };
  }, [startPress, endPress]);

  let pressStartTime: number;

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Head>
        <title>モールス信号練習</title>
        <meta name="description" content="モールス信号練習アプリ" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <Container maxWidth="sm">
        <Box sx={{ my: 4, textAlign: 'center' }}>
          <Typography variant="h3" component="h1" gutterBottom>
            モールス信号練習
          </Typography>
          <Typography variant="body1" gutterBottom>
            クリックまたはスペースキーで入力<br />
            短押し: ・ 長押し: -
          </Typography>

          <Button
            variant="contained"
            color="primary"
            size="large"
            sx={{
              width: 200,
              height: 200,
              borderRadius: '50%',
              fontSize: '1.2rem',
              my: 2,
              textTransform: 'none',
              backgroundColor: isPressed ? theme.palette.primary.dark : theme.palette.primary.main,
            }}
            onMouseDown={() => {
              pressStartTime = new Date().getTime();
              startPress();
            }}
            onMouseUp={endPress}
            onMouseLeave={endPress}
          >
            ここをクリック<br />またはスペースキー
          </Button>

          <Paper elevation={3} sx={{ p: 2, my: 2 }}>
            <Typography variant="h6" gutterBottom>
              入力:
            </Typography>
            <Typography variant="body1">
              {morseInput + currentLetter}
            </Typography>
          </Paper>

          <Paper elevation={3} sx={{ p: 2, my: 2 }}>
            <Typography variant="h6" gutterBottom>
              変換結果:
            </Typography>
            <Typography variant="body1">
              {result}
            </Typography>
          </Paper>

          <Button
            variant="contained"
            color="secondary"
            startIcon={<RefreshIcon />}
            onClick={resetInput}
          >
            リセット
          </Button>
        </Box>
      </Container>
    </ThemeProvider>
  );
};

export default Home;