본문 바로가기

프로젝트/개인프로젝트

React.js + Spring Boot를 이용한 스토쿠 정답 확인하기

 

개인 프로젝트 개발 계기

 

최근 공부하다가 어떻게 놀까해서 스토쿠 책을 구매했다.

그런데 너무 어려워서 1시간이 걸리다보니 스도쿠 문제 풀어주는 웹 페이지가 있으면 좋겠다 싶어서 개발을 시작했다.

 

사용한 개발 툴

  • Front : React
  • Back : Spring Boot

 

추가로 적용할 것

AWS EC2로 배포

 


React.js와 Spring Boot를 하다보니 CORS 에러가 뜬다.

 

이에 대한 해결 방법으론 SecurityConfig, WebConfig 클래스를 만들어 해결한다.

 

# WebConfig.java

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000") // React 애플리케이션의 주소
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true);
    }
}

 

# SecurityConfig.java

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors().and() 
                .csrf().disable(); 
        return http.build();
    }
}

 

 

- 이 두 클래스를 적용하면 CORS가 해결된다!!!

 

# 행 확인하는 메소드

private boolean isValidRow(int[][] board, int row) {
        Set<Integer> set = new HashSet<>();
        for (int j = 0; j < 9; j++) {
            int value = board[row][j];
            if (value == 0) {
                return false; // 하나라도 값이 입력되지 않았으면 false 반환
            }
            if (!set.add(value)) {
                return false; // 중복된 값이 존재하면 false 반환
            }
        }
        return true;
    }

 

# 열 확인하는 코드

private boolean isValidColumn(int[][] board, int col) {
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < 9; i++) {
            int value = board[i][col];
            if (value == 0) {
                return false; // 하나라도 값이 입력되지 않았으면 false 반환
            }
            if (!set.add(value)) {
                return false; // 중복된 값이 존재하면 false 반환
            }
        }
        return true;
    }

 

 

# 3x3 작은 정사각형 확인하는 코드

private boolean isValidBox(int[][] board, int startRow, int startCol) {
        Set<Integer> set = new HashSet<>();
        for (int i = startRow; i < startRow + 3; i++) {
            for (int j = startCol; j < startCol + 3; j++) {
                int value = board[i][j];
                if (value == 0) {
                    return false; // 하나라도 값이 입력되지 않았으면 false 반환
                }
                if (!set.add(value)) {
                    return false; // 중복된 값이 존재하면 false 반환
                }
            }
        }
        return true;
    }

 

- 컨트롤러 한개만 만들어서 구현

 

 

# 스도쿠 초기 설정을 위한 배열 선언
const initialGrid: number[][] = Array.from({ length: 9 }, () => Array(9).fill(0));

 

# 스도쿠 해결하는 재귀함수
# 빈 칸에 1~9 수를 채워가며 가능한 수를 탐색하여 퍼즐 완성
const solveSudoku = (board: number[][]) => {
	# 주어진 위치에 특정 숫자가 유효한지 확인하는 함수
    const isValid = (row: number, col: number, num: number) => {
    	# 주어진 숫자가 같은 행이나 열에 이미 존재하는지 확인
        for (let i = 0; i < 9; i++) {
            if (board[row][i] === num || board[i][col] === num) {
                return false;
            }
        }
        # 3X3 작은 정사각형의 시작 위치를 계산
        const startRow = Math.floor(row / 3) * 3;
        const startCol = Math.floor(col / 3) * 3;
        
        # 주어진 숫자가 작은 정사각형 안에 이미 존재하는지 확인 
        for (let i = startRow; i < startRow + 3; i++) {
            for (let j = startCol; j < startCol + 3; j++) {
                if (board[i][j] === num) {
                    return false;
                }
            }
        }
        return true;
    };

	# 스도쿠 해결하는 메인 함수
    const solve = (row: number, col: number): boolean => {
    
    	# 모든 행 확인하면 해결
        if (row === 9) {
            return true;
        }
        
        # 이미 숫자가 채워져 있는 경우, 다음 위치로 이동하여 스도쿠 해결
        if (board[row][col] !== 0) {
            const nextRow = col === 8 ? row + 1 : row;
            const nextCol = col === 8 ? 0 : col + 1;
            return solve(nextRow, nextCol);
        }
        
        # 현재 위치에 숫자를 채워 넣는 부분
        # 주어진 숫자가 유효하면 재귀적으로 다음 위치로 이동
        for (let num = 1; num <= 9; num++) {
            if (isValid(row, col, num)) {
                board[row][col] = num;
                const nextRow = col === 8 ? row + 1 : row;
                const nextCol = col === 8 ? 0 : col + 1;
                if (solve(nextRow, nextCol)) {
                    return true;
                }
                board[row][col] = 0;
            }
        }
        return false;
    };
	
    # 마지막으로 solve 함수를 호출
    solve(0, 0);
};

 

# 스도쿠 그리드가 유효한지 확인
const isValidSudoku = (grid: number[][]): boolean => {
	# 숫자 배열이 유효한지 확인
    const isValid = (arr: number[]): boolean => {
    	# 배열에서 0을 제외한 숫자들을 필터링하고 중복된 숫자 확인
        const filtered = arr.filter(num => num !== 0);
        return new Set(filtered).size === filtered.length;
    };

	# 행과 열이 유효한지 확인
    for (let i = 0; i < 9; i++) {
        if (!isValid(grid[i])) return false;
        if (!isValid(grid.map(row => row[i]))) return false;
        
        # 3X3 박스가 유효한지 확인
        const startRow = Math.floor(i / 3) * 3;
        const startCol = (i % 3) * 3;
        const box = grid.slice(startRow, startRow + 3).flatMap(row => row.slice(startCol, startCol + 3));
        if (!isValid(box)) return false;
    }

    return true;
};

 

# 최상위 컴포넌트
const App: React.FC = () => {
	# 초기 스도쿠 그리드 상태값
    const [grid, setGrid] = useState(initialGrid);
	
    # 입력된 스도쿠 셀 값이 변경될 때 호출
    const handleChange = (row: number, col: number, value: string) => {
        const newGrid = grid.map((rowArr, r) =>
            rowArr.map((cell, c) => (r === row && c === col ? Number(value) : cell))
        );
        setGrid(newGrid);
    };
	
    # 스도쿠 검증하기 위해 호출
    const handleSubmit = () => {
        axios.post('http://localhost:8080/api/validate', grid)
            .then(response => {
                if (response.data) {
                    alert('유효한 스토쿠입니다!');
                } else {
                    alert('유효하지 않은 스토쿠입니다!');
                }
            })
            .catch(error => {
                console.error('Error validating sudoku:', error);
            });
    };
	
    # 스도쿠 정답을 보여주기 위해 호출
    const showSolution = () => {
        const solvedGrid = [...grid];
        solveSudoku(solvedGrid);
        setGrid(solvedGrid);
    };

    return (
        <div className="App">
            <h1>스토쿠 풀이기</h1>
            <div className="grid">
                {grid.map((row, r) => (
                    <div key={r} className="row">
                        {row.map((cell, c) => (
                            <input
                                key={c}
                                type="number"
                                min="0"
                                max="9"
                                value={cell || ''}
                                onChange={(e) => handleChange(r, c, e.target.value)}
                            />
                        ))}
                    </div>
                ))}
            </div>
            <button onClick={handleSubmit}>검증하기</button>
            <button onClick={showSolution}>정답보기</button>
        </div>
    );
};

 


포스팅을 마치며

1시간동안 스도쿠를 풀었는데,

이 것은 버튼 딸깍에 바로 풀어버린다.

1초만에 풀어버린다.

내 1시간!!!!

 

이제 EC2로 배포해보고 이번 개인 프로젝트는 마쳐야겠다.