diff --git a/build.gradle b/build.gradle index 41dab873c1..f0ffff951f 100644 --- a/build.gradle +++ b/build.gradle @@ -13,9 +13,6 @@ repositories { } dependencies { - implementation 'com.sparkjava:spark-core:2.9.3' - implementation 'com.sparkjava:spark-template-handlebars:2.7.1' - implementation 'ch.qos.logback:logback-classic:1.2.10' testImplementation 'org.assertj:assertj-core:3.22.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' diff --git a/docker/db/mysql/init/init.sql b/docker/db/mysql/init/init.sql index 7e606e8cc3..faca73e165 100644 --- a/docker/db/mysql/init/init.sql +++ b/docker/db/mysql/init/init.sql @@ -1,3 +1,12 @@ +create table game +( + id int auto_increment + primary key, + title varchar(20) not null, + password varchar(20) not null, + state varchar(20) not null +); + create table board ( position varchar(5) not null, @@ -9,10 +18,3 @@ create table board foreign key (game_id) references game (id) on update cascade on delete cascade ); - -create table game -( - id int auto_increment - primary key, - state varchar(20) not null -); diff --git a/src/main/java/chess/controller/ErrorController.java b/src/main/java/chess/controller/ErrorController.java index 3b48b69e61..758f10087f 100644 --- a/src/main/java/chess/controller/ErrorController.java +++ b/src/main/java/chess/controller/ErrorController.java @@ -1,5 +1,6 @@ package chess.controller; +import org.springframework.beans.BeanInstantiationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -8,6 +9,11 @@ @ControllerAdvice public class ErrorController { + @ExceptionHandler(BeanInstantiationException.class) + public ResponseEntity errorBeanResponse(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + @ExceptionHandler({IllegalStateException.class, IllegalArgumentException.class}) public ResponseEntity errorResponse(Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); diff --git a/src/main/java/chess/controller/WebController.java b/src/main/java/chess/controller/WebController.java index cdae1f098f..8d13a46873 100644 --- a/src/main/java/chess/controller/WebController.java +++ b/src/main/java/chess/controller/WebController.java @@ -1,64 +1,88 @@ package chess.controller; +import chess.domain.chessboard.ChessBoard; +import chess.domain.piece.generator.NormalPiecesGenerator; import chess.dto.BoardDto; +import chess.dto.GameDto; import chess.dto.MoveDto; +import chess.dto.RoomDto; import chess.dto.StatusDto; import chess.service.ChessService; -import java.util.HashMap; -import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; @Controller public class WebController { private final ChessService chessService; - @Autowired public WebController(ChessService chessService) { this.chessService = chessService; } @GetMapping("/") - public ModelAndView selectGame() { - BoardDto boardDto = chessService.selectBoard(); - String winner = chessService.selectWinner(); + public String selectGame(Model model) { + List games = chessService.findGame(); + model.addAttribute("game", games); + return "index"; + } + + @PostMapping("/game") + public String insertGame(RoomDto roomDto) { + chessService.insertGame(roomDto, new ChessBoard(new NormalPiecesGenerator())); + return "redirect:/"; + } - Map model = new HashMap<>(); - model.put("board", boardDto); - model.put("winner", winner); + @GetMapping("/game/{gameId}") + public String startGame(@PathVariable int gameId, Model model) { + BoardDto boardDto = chessService.selectBoard(gameId); + String winner = chessService.selectWinner(gameId); + String state = chessService.selectState(gameId); - return new ModelAndView("index", model); + model.addAttribute("board", boardDto); + model.addAttribute("id", gameId); + model.addAttribute("winner", winner); + model.addAttribute("state", state); + + return "game"; } - @GetMapping("/game") - public ModelAndView insertGame() { - chessService.insertGame(); - return new ModelAndView("redirect:/"); + @PutMapping("/game/board/{gameId}") + public String movePiece(@PathVariable int gameId, MoveDto moveDto) { + chessService.movePiece(gameId, moveDto.getFrom(), moveDto.getTo()); + return "redirect:/game/" + gameId; } - @PutMapping("/game/board") - public ModelAndView updateBoard(MoveDto moveDto) { - chessService.updateBoard(moveDto.getFrom(), moveDto.getTo()); - return new ModelAndView("redirect:/"); + @PutMapping("/game/{gameId}") + public String restartGame(@PathVariable int gameId) { + chessService.restartGame(gameId); + return "redirect:/game/" + gameId; } - @GetMapping("/game/status") + @GetMapping("/game/status/{gameId}") @ResponseBody - public ResponseEntity selectStatus() { - StatusDto statusDto = chessService.selectStatus(); + public ResponseEntity selectStatus(@PathVariable int gameId) { + StatusDto statusDto = chessService.selectStatus(gameId); return ResponseEntity.ok().body(statusDto); } - @DeleteMapping("/game") - public ModelAndView deleteGame() { - chessService.deleteGame(); - return new ModelAndView("redirect:/"); + @PutMapping("/game/end/{gameId}") + public String endGame(@PathVariable int gameId) { + chessService.endGame(gameId); + return "redirect:/"; + } + + @DeleteMapping("/game/{gameId}") + public String deleteGame(@PathVariable int gameId, String password) { + chessService.deleteGame(gameId, password); + return "redirect:/"; } } diff --git a/src/main/java/chess/dao/BoardDao.java b/src/main/java/chess/dao/BoardDao.java new file mode 100644 index 0000000000..c9bd69280a --- /dev/null +++ b/src/main/java/chess/dao/BoardDao.java @@ -0,0 +1,15 @@ +package chess.dao; + +import chess.entity.Square; +import java.util.List; + +public interface BoardDao { + + void save(List squares); + + List findById(int id); + + int update(Square square); + + void delete(int gameId); +} diff --git a/src/main/java/chess/dao/BoardDaoImpl.java b/src/main/java/chess/dao/BoardDaoImpl.java new file mode 100644 index 0000000000..1a900ce210 --- /dev/null +++ b/src/main/java/chess/dao/BoardDaoImpl.java @@ -0,0 +1,59 @@ +package chess.dao; + +import chess.entity.Square; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository +public class BoardDaoImpl implements BoardDao { + + private JdbcTemplate jdbcTemplate; + + private RowMapper rowMapper = (rs, rowNum) -> + new Square( + rs.getString("position"), + rs.getString("symbol"), + rs.getString("color") + ); + + public BoardDaoImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void save(List squares) { + String sql = "insert into board (position, symbol, color, game_id) values (?, ?, ?, ?)"; + + List board = squares.stream() + .map(square -> new Object[]{ + square.getPosition(), + square.getSymbol(), + square.getColor(), + square.getGameId()}) + .collect(Collectors.toList()); + + jdbcTemplate.batchUpdate(sql, board); + } + + @Override + public List findById(int id) { + String sql = "select position, symbol, color from board where game_id = ?"; + return jdbcTemplate.query(sql, rowMapper, id); + } + + @Override + public int update(Square square) { + String sql = "update board set symbol = ?, color = ? where game_id = ? and position = ?"; + return jdbcTemplate.update(sql, square.getSymbol(), square.getColor(), square.getGameId(), + square.getPosition()); + } + + @Override + public void delete(int gameId) { + String sql = "delete from board where game_id = ?"; + jdbcTemplate.update(sql, gameId); + } +} diff --git a/src/main/java/chess/dao/GameDao.java b/src/main/java/chess/dao/GameDao.java new file mode 100644 index 0000000000..1815a198cf --- /dev/null +++ b/src/main/java/chess/dao/GameDao.java @@ -0,0 +1,17 @@ +package chess.dao; + +import chess.entity.Game; +import java.util.List; + +public interface GameDao { + + int save(Game game); + + List findAll(); + + Game findById(int id); + + int update(Game game); + + int delete(int id); +} diff --git a/src/main/java/chess/dao/GameDaoImpl.java b/src/main/java/chess/dao/GameDaoImpl.java new file mode 100644 index 0000000000..6029611bde --- /dev/null +++ b/src/main/java/chess/dao/GameDaoImpl.java @@ -0,0 +1,65 @@ +package chess.dao; + +import chess.entity.Game; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; + +@Repository +public class GameDaoImpl implements GameDao { + + private JdbcTemplate jdbcTemplate; + + private RowMapper rowMapper = (rs, rowNum) -> + new Game( + rs.getInt("id"), + rs.getString("title"), + rs.getString("password"), + rs.getString("state") + ); + + public GameDaoImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public int save(Game game) { + SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate).withTableName("game") + .usingGeneratedKeyColumns("id"); + Map params = new HashMap<>(); + params.put("title", game.getTitle()); + params.put("password", game.getPassword()); + params.put("state", game.getState()); + + return simpleJdbcInsert.executeAndReturnKey(params).intValue(); + } + + @Override + public List findAll() { + String sql = "select * from game"; + return jdbcTemplate.query(sql, rowMapper); + } + + @Override + public Game findById(int id) { + String sql = "select * from game where id = ?"; + return jdbcTemplate.queryForObject(sql, rowMapper, id); + } + + @Override + public int update(Game game) { + String sql = "update game set state=? where id = ?"; + jdbcTemplate.update(sql, game.getState(), game.getId()); + return game.getId(); + } + + @Override + public int delete(int id) { + String sql = "delete from game where id = ?"; + return jdbcTemplate.update(sql, id); + } +} diff --git a/src/main/java/chess/domain/chessboard/ChessBoard.java b/src/main/java/chess/domain/chessboard/ChessBoard.java index 3be8fdb597..ab82479983 100644 --- a/src/main/java/chess/domain/chessboard/ChessBoard.java +++ b/src/main/java/chess/domain/chessboard/ChessBoard.java @@ -125,10 +125,4 @@ public Color getWinner() { public Map getPieces() { return Collections.unmodifiableMap(pieces); } - - public Map toMap() { - return pieces.entrySet() - .stream() - .collect(Collectors.toMap(m -> m.getKey().toString(), Map.Entry::getValue)); - } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 1e97928f6a..093130f2d4 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -2,6 +2,7 @@ import chess.domain.position.Direction; import chess.domain.position.Position; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -11,19 +12,8 @@ public abstract class Piece { private static final Map cache = new HashMap<>(); static { - cache.put("WHITEKING", new King(Color.WHITE)); - cache.put("BLACKKING", new King(Color.BLACK)); - cache.put("WHITEQUEEN", new Queen(Color.WHITE)); - cache.put("BLACKQUEEN", new Queen(Color.BLACK)); - cache.put("WHITEROOK", new Rook(Color.WHITE)); - cache.put("BLACKROOK", new Rook(Color.BLACK)); - cache.put("WHITEKNIGHT", new Knight(Color.WHITE)); - cache.put("BLACKKNIGHT", new Knight(Color.BLACK)); - cache.put("WHITEBISHOP", new Bishop(Color.WHITE)); - cache.put("BLACKBISHOP", new Bishop(Color.BLACK)); - cache.put("WHITEPAWN", new Pawn(Color.WHITE)); - cache.put("BLACKPAWN", new Pawn(Color.BLACK)); - cache.put("EMPTYEMPTY", EmptyPiece.getInstance()); + Arrays.stream(PieceName.values()).forEach( + gamePiece -> cache.put(gamePiece.getValue(), gamePiece.getPiece())); } protected final Symbol symbol; diff --git a/src/main/java/chess/domain/piece/PieceName.java b/src/main/java/chess/domain/piece/PieceName.java new file mode 100644 index 0000000000..219c98c93a --- /dev/null +++ b/src/main/java/chess/domain/piece/PieceName.java @@ -0,0 +1,33 @@ +package chess.domain.piece; + +public enum PieceName { + WHITE_KING("WHITEKING", new King(Color.WHITE)), + BLACK_KING("BLACKKING", new King(Color.BLACK)), + WHITE_QUEEN("WHITEQUEEN", new Queen(Color.WHITE)), + BLACK_QUEEN("BLACKQUEEN", new Queen(Color.BLACK)), + WHITE_ROOK("WHITEROOK", new Rook(Color.WHITE)), + BLACK_ROOK("BLACKROOK", new Rook(Color.BLACK)), + WHITE_KNIGHT("WHITEKNIGHT", new Knight(Color.WHITE)), + BLACK_KNIGHT("BLACKKNIGHT", new Knight(Color.BLACK)), + WHITE_BISHOP("WHITEBISHOP", new Bishop(Color.WHITE)), + BLACK_BISHOP("BLACKBISHOP", new Bishop(Color.BLACK)), + WHITE_PAWN("WHITEPAWN", new Pawn(Color.WHITE)), + BLACK_PAWN("BLACKPAWN", new Pawn(Color.BLACK)), + EMPTY("EMPTYEMPTY", EmptyPiece.getInstance()); + + private final String value; + private final Piece piece; + + PieceName(String value, Piece piece) { + this.value = value; + this.piece = piece; + } + + public String getValue() { + return value; + } + + public Piece getPiece() { + return piece; + } +} diff --git a/src/main/java/chess/domain/position/Column.java b/src/main/java/chess/domain/position/Column.java index aa3b49868e..8721c855f6 100644 --- a/src/main/java/chess/domain/position/Column.java +++ b/src/main/java/chess/domain/position/Column.java @@ -46,9 +46,4 @@ public Column move(final int value) { public int getValue() { return value; } - - @Override - public String toString() { - return name().toLowerCase(Locale.ROOT); - } } diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java index 9fb960cc64..8fe94a2d0f 100644 --- a/src/main/java/chess/domain/position/Position.java +++ b/src/main/java/chess/domain/position/Position.java @@ -101,8 +101,15 @@ public int hashCode() { return Objects.hash(column, row); } + public String getValue() { + return column.name().toLowerCase(Locale.ROOT) + row.getValue(); + } + @Override public String toString() { - return column.toString() + row.toString(); + return "Position{" + + "column=" + column + + ", row=" + row + + '}'; } } diff --git a/src/main/java/chess/domain/position/Row.java b/src/main/java/chess/domain/position/Row.java index 083c559914..96176c4723 100644 --- a/src/main/java/chess/domain/position/Row.java +++ b/src/main/java/chess/domain/position/Row.java @@ -48,9 +48,4 @@ public Row move(final int value) { public int getValue() { return value; } - - @Override - public String toString() { - return Integer.toString(value); - } } diff --git a/src/main/java/chess/domain/state/BlackRunning.java b/src/main/java/chess/domain/state/BlackRunning.java index bcb845e809..8cf2ecf255 100644 --- a/src/main/java/chess/domain/state/BlackRunning.java +++ b/src/main/java/chess/domain/state/BlackRunning.java @@ -26,7 +26,7 @@ private boolean isBlackTurn(final ChessBoard chessBoard, final GameCommand gameC } @Override - public String toString() { - return "BlackRunning"; + public String getValue() { + return StateName.BLACK_TURN.getValue(); } } diff --git a/src/main/java/chess/domain/state/Finish.java b/src/main/java/chess/domain/state/Finish.java index 9c4333ce64..a6fcad7f2a 100644 --- a/src/main/java/chess/domain/state/Finish.java +++ b/src/main/java/chess/domain/state/Finish.java @@ -24,7 +24,7 @@ public boolean isFinished() { } @Override - public String toString() { - return "Finish"; + public String getValue() { + return StateName.FINISH.getValue(); } } diff --git a/src/main/java/chess/domain/state/Ready.java b/src/main/java/chess/domain/state/Ready.java index 13568ca0f2..d4ad8a3af3 100644 --- a/src/main/java/chess/domain/state/Ready.java +++ b/src/main/java/chess/domain/state/Ready.java @@ -31,7 +31,7 @@ public boolean isFinished() { } @Override - public String toString() { + public String getValue() { return "Ready"; } } diff --git a/src/main/java/chess/domain/state/State.java b/src/main/java/chess/domain/state/State.java index e0c8b049d7..c0e9b411cc 100644 --- a/src/main/java/chess/domain/state/State.java +++ b/src/main/java/chess/domain/state/State.java @@ -2,23 +2,21 @@ import chess.domain.chessboard.ChessBoard; import chess.domain.command.GameCommand; +import java.util.Arrays; public interface State { State proceed(final ChessBoard chessBoard, final GameCommand gameCommand); static State of(String value) { - if (value.equals("BlackRunning")) { - return new BlackRunning(); - } - if (value.equals("WhiteRunning")) { - return new WhiteRunning(); - } - if (value.equals("Finish")) { - return new Finish(); - } - throw new IllegalArgumentException("게임 진행 상태가 없습니다."); + StateName gameState = Arrays.stream(StateName.values()) + .filter(state -> state.isSame(value)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("게임 진행 상태가 없습니다.")); + return gameState.getState(); } boolean isFinished(); + + String getValue(); } diff --git a/src/main/java/chess/domain/state/StateName.java b/src/main/java/chess/domain/state/StateName.java new file mode 100644 index 0000000000..db8c6afb0f --- /dev/null +++ b/src/main/java/chess/domain/state/StateName.java @@ -0,0 +1,27 @@ +package chess.domain.state; + +public enum StateName { + BLACK_TURN("BlackTurn", new BlackRunning()), + WHITE_TURN("WhiteTurn", new WhiteRunning()), + FINISH("Finish", new Finish()); + + private final String value; + private final State state; + + StateName(String value, State state) { + this.value = value; + this.state = state; + } + + public boolean isSame(String value) { + return this.value.equals(value); + } + + public String getValue() { + return value; + } + + public State getState() { + return state; + } +} diff --git a/src/main/java/chess/domain/state/WhiteRunning.java b/src/main/java/chess/domain/state/WhiteRunning.java index fd74a21cf6..048650931c 100644 --- a/src/main/java/chess/domain/state/WhiteRunning.java +++ b/src/main/java/chess/domain/state/WhiteRunning.java @@ -26,7 +26,7 @@ private boolean isWhiteTurn(final ChessBoard chessBoard, final GameCommand gameC } @Override - public String toString() { - return "WhiteRunning"; + public String getValue() { + return StateName.WHITE_TURN.getValue(); } } diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java index e174ce5808..dba6d112cb 100644 --- a/src/main/java/chess/dto/BoardDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -1,6 +1,12 @@ package chess.dto; +import chess.domain.chessboard.ChessBoard; +import chess.domain.piece.Piece; +import chess.domain.position.Position; +import chess.entity.Square; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class BoardDto { @@ -10,6 +16,22 @@ public BoardDto(Map board) { this.board = board; } + public static BoardDto of(List squares) { + Map newBoard = squares.stream() + .collect(Collectors.toMap(Square::getPosition, + square -> PieceDto.from(Piece.of(square.getColor(), square.getSymbol())))); + return new BoardDto(newBoard); + } + + public static BoardDto from(ChessBoard chessBoard) { + Map pieces = chessBoard.getPieces(); + Map board = pieces.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().getValue(), + entry -> PieceDto.from(entry.getValue()))); + return new BoardDto(board); + } + public Map getBoard() { return board; } diff --git a/src/main/java/chess/dto/GameDto.java b/src/main/java/chess/dto/GameDto.java new file mode 100644 index 0000000000..1c2ec683b4 --- /dev/null +++ b/src/main/java/chess/dto/GameDto.java @@ -0,0 +1,32 @@ +package chess.dto; + +import chess.entity.Game; + +public class GameDto { + + private int id; + private String title; + private String state; + + private GameDto(int id, String title, String state) { + this.id = id; + this.title = title; + this.state = state; + } + + public static GameDto of(Game game) { + return new GameDto(game.getId(), game.getTitle(), game.getState()); + } + + public int getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getState() { + return state; + } +} diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java index c17f530bf8..8e8186d11d 100644 --- a/src/main/java/chess/dto/PieceDto.java +++ b/src/main/java/chess/dto/PieceDto.java @@ -1,5 +1,7 @@ package chess.dto; +import chess.domain.piece.Piece; + public class PieceDto { private String symbol; @@ -10,6 +12,14 @@ public PieceDto(String symbol, String color) { this.color = color; } + public static PieceDto from(Piece piece) { + return new PieceDto(piece.getSymbol().name(), piece.getColor().name()); + } + + public Piece toPiece() { + return Piece.of(color, symbol); + } + public String getSymbol() { return symbol; } diff --git a/src/main/java/chess/dto/RoomDto.java b/src/main/java/chess/dto/RoomDto.java new file mode 100644 index 0000000000..dbec7edef8 --- /dev/null +++ b/src/main/java/chess/dto/RoomDto.java @@ -0,0 +1,40 @@ +package chess.dto; + +public class RoomDto { + + private static final int MAX_TITLE_LENGTH = 20; + private static final int MAX_PASSWORD_LENGTH = 20; + + private String title; + private String password; + + public RoomDto(String title, String password) { + validateTitle(title); + validatePassword(password); + this.title = title; + this.password = password; + } + + private void validateTitle(String title) { + if (title.isBlank()) { + throw new IllegalArgumentException("방 이름을 입력해주세요."); + } + if (title.length() > MAX_TITLE_LENGTH) { + throw new IllegalArgumentException("방 이름의 최대 길이는 20입니다."); + } + } + + private void validatePassword(String password) { + if (password.length() > MAX_PASSWORD_LENGTH) { + throw new IllegalArgumentException("비밀번호의 최대 길이는 20입니다."); + } + } + + public String getPassword() { + return password; + } + + public String getTitle() { + return title; + } +} diff --git a/src/main/java/chess/dto/SquareDto.java b/src/main/java/chess/dto/SquareDto.java deleted file mode 100644 index c3de8cedc1..0000000000 --- a/src/main/java/chess/dto/SquareDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package chess.dto; - -public class SquareDto { - - private String position; - private String symbol; - private String color; - - public SquareDto(String position, String symbol, String color) { - this.position = position; - this.symbol = symbol; - this.color = color; - } - - public String getSymbol() { - return symbol; - } - - public String getColor() { - return color; - } - - public String getPosition() { - return position; - } -} diff --git a/src/main/java/chess/dto/StatusDto.java b/src/main/java/chess/dto/StatusDto.java index c5d5bbeee7..7a4e541256 100644 --- a/src/main/java/chess/dto/StatusDto.java +++ b/src/main/java/chess/dto/StatusDto.java @@ -1,5 +1,8 @@ package chess.dto; +import chess.domain.piece.Color; +import java.util.Map; + public class StatusDto { private String whiteScore; @@ -10,8 +13,8 @@ public StatusDto(String whiteScore, String blackScore) { this.blackScore = blackScore; } - public StatusDto(Double whiteScore, Double blackScore) { - this(String.valueOf(whiteScore), String.valueOf(blackScore)); + public StatusDto(Map scores) { + this(String.valueOf(scores.get(Color.WHITE)), String.valueOf(scores.get(Color.BLACK))); } public String getWhiteScore() { diff --git a/src/main/java/chess/entity/Game.java b/src/main/java/chess/entity/Game.java new file mode 100644 index 0000000000..3a10bb7988 --- /dev/null +++ b/src/main/java/chess/entity/Game.java @@ -0,0 +1,57 @@ +package chess.entity; + +public class Game { + + private int id; + private String title; + private String password; + private String state; + + public Game(int id, String title, String password, String state) { + this.id = id; + this.title = title; + this.password = password; + this.state = state; + } + + public Game(String title, String password, String state) { + this.title = title; + this.password = password; + this.state = state; + } + + public Game(String state, int id) { + this.state = state; + this.id = id; + } + + public boolean isSamePassword(String password) { + return this.password.equals(password); + } + + public int getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getPassword() { + return password; + } + + public String getState() { + return state; + } + + @Override + public String toString() { + return "Game{" + + "id=" + id + + ", title='" + title + '\'' + + ", password='" + password + '\'' + + ", state='" + state + '\'' + + '}'; + } +} diff --git a/src/main/java/chess/entity/Square.java b/src/main/java/chess/entity/Square.java new file mode 100644 index 0000000000..68d3217663 --- /dev/null +++ b/src/main/java/chess/entity/Square.java @@ -0,0 +1,79 @@ +package chess.entity; + +import chess.domain.chessboard.ChessBoard; +import chess.domain.piece.Piece; +import chess.domain.position.Position; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class Square { + + private String position; + private String symbol; + private String color; + private int gameId; + + public Square(String position, Piece piece, int gameId) { + this.position = position; + this.symbol = piece.getSymbol().name(); + this.color = piece.getColor().name(); + this.gameId = gameId; + } + + public Square(String position, String symbol, String color, int gameId) { + this.position = position; + this.symbol = symbol; + this.color = color; + this.gameId = gameId; + } + + public Square(String position, String symbol, String color) { + this.position = position; + this.symbol = symbol; + this.color = color; + } + + public static List from(ChessBoard chessBoard, int gameId) { + Map pieces = chessBoard.getPieces(); + List squares = pieces.entrySet().stream() + .map(entry -> new Square(entry.getKey().getValue(), entry.getValue().getSymbol().name(), + entry.getValue().getColor().name(), gameId)) + .collect(Collectors.toList()); + return squares; + } + + public static ChessBoard toBoard(List squares) { + Map pieces = squares.stream() + .collect(Collectors.toMap( + square -> Position.of(square.position), + square -> Piece.of(square.color, square.symbol))); + return new ChessBoard(pieces); + } + + public String getPosition() { + return position; + } + + public String getSymbol() { + return symbol; + } + + public String getColor() { + return color; + } + + public int getGameId() { + return gameId; + } + + @Override + public String toString() { + return "Square{" + + "position='" + position + '\'' + + ", symbol='" + symbol + '\'' + + ", color='" + color + '\'' + + ", gameId=" + gameId + + '}'; + } +} diff --git a/src/main/java/chess/repository/BoardDao.java b/src/main/java/chess/repository/BoardDao.java deleted file mode 100644 index 19611654de..0000000000 --- a/src/main/java/chess/repository/BoardDao.java +++ /dev/null @@ -1,48 +0,0 @@ -package chess.repository; - -import chess.domain.chessboard.ChessBoard; -import chess.domain.piece.Piece; -import chess.domain.position.Position; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; - -@Repository -public class BoardDao { - - @Autowired - private JdbcTemplate jdbcTemplate; - - public void save(ChessBoard chessBoard, Long gameId) { - String sql = "insert into board (position, symbol, color, game_id) values (?, ?, ?, ?)"; - - List board = chessBoard.getPieces().entrySet().stream() - .map(entry -> new Object[]{entry.getKey().toString(), entry.getValue().getSymbol().name(), - entry.getValue().getColor().name(), gameId}) - .collect(Collectors.toList()); - - jdbcTemplate.batchUpdate(sql, board); - } - - public ChessBoard find() { - String sql = "select position, symbol, color from board"; - - List> squares = jdbcTemplate.queryForList(sql); - Map board = new HashMap<>(); - for (Map square : squares) { - board.put( - Position.of((String) square.get("position")), - Piece.of((String) square.get("color"), (String) square.get("symbol"))); - } - return new ChessBoard(board); - } - - public int update(Position position, Piece piece, Long gameId) { - String sql = "update board set symbol = (?), color = (?) where game_id = (?) and position = (?)"; - return jdbcTemplate.update(sql, piece.getSymbol().name(), piece.getColor().name(), gameId, position.toString()); - } -} diff --git a/src/main/java/chess/repository/GameDao.java b/src/main/java/chess/repository/GameDao.java deleted file mode 100644 index 39e84bd4ab..0000000000 --- a/src/main/java/chess/repository/GameDao.java +++ /dev/null @@ -1,48 +0,0 @@ -package chess.repository; - -import chess.domain.state.State; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; - -@Repository -public class GameDao { - - @Autowired - private JdbcTemplate jdbcTemplate; - - public void save(String state) { - String sql = "insert into game(state) values (?)"; - jdbcTemplate.update(sql, state); - } - - public State findState() { - String sql = "select state from game"; - return jdbcTemplate.queryForObject( - sql, - (rs, rowNum) -> { - State state = State.of(rs.getString("state")); - return state; - }); - } - - public Long findId() { - String sql = "select id from game order by id DESC LIMIT 1"; - return jdbcTemplate.queryForObject(sql, Long.class); - } - - public int update(String state, Long gameId) { - String sql = "update game set state=? where id=?"; - return jdbcTemplate.update(sql, state, gameId); - } - - public int delete() { - String sql = "delete from game"; - return jdbcTemplate.update(sql); - } - - public Long findGameCount() { - String sql = "select count(*) from game"; - return jdbcTemplate.queryForObject(sql, Long.class); - } -} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 744b0ee1e4..bc193dba2a 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,22 +1,25 @@ package chess.service; -import chess.repository.BoardDao; -import chess.repository.GameDao; +import chess.dao.BoardDao; +import chess.dao.GameDao; import chess.domain.chessboard.ChessBoard; import chess.domain.command.GameCommand; import chess.domain.game.ChessGame; import chess.domain.piece.Color; import chess.domain.piece.EmptyPiece; -import chess.domain.piece.Piece; import chess.domain.piece.generator.NormalPiecesGenerator; import chess.domain.position.Position; import chess.domain.state.State; +import chess.domain.state.StateName; import chess.dto.BoardDto; -import chess.dto.PieceDto; +import chess.dto.GameDto; +import chess.dto.RoomDto; import chess.dto.StatusDto; -import java.util.HashMap; +import chess.entity.Game; +import chess.entity.Square; +import java.util.List; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,74 +29,95 @@ public class ChessService { private final GameDao gameDao; private final BoardDao boardDao; - @Autowired public ChessService(GameDao gameDao, BoardDao boardDao) { this.gameDao = gameDao; this.boardDao = boardDao; } - public BoardDto selectBoard() { - ChessBoard chessBoard = boardDao.find(); - Map pieces = chessBoard.getPieces(); - Map board = new HashMap<>(); - for (Position position : pieces.keySet()) { - String key = position.toString(); - Piece piece = pieces.get(Position.of(key)); - PieceDto pieceDto = new PieceDto(piece.getSymbol().name(), piece.getColor().name()); - board.put(key, pieceDto); - } - return new BoardDto(board); + @Transactional + public int insertGame(RoomDto roomDto, ChessBoard chessBoard) { + int id = gameDao.save(new Game(roomDto.getTitle(), roomDto.getPassword(), StateName.WHITE_TURN.getValue())); + boardDao.save(Square.from(chessBoard, id)); + return id; } - public String selectWinner() { - if (gameDao.findGameCount() == 0) { - return null; - } - ChessBoard chessBoard = boardDao.find(); - State state = gameDao.findState(); + public List findGame() { + return gameDao.findAll().stream() + .map(GameDto::of) + .collect(Collectors.toList()); + } + + public BoardDto selectBoard(int gameId) { + return BoardDto.of(boardDao.findById(gameId)); + } - ChessGame chessGame = new ChessGame(state, chessBoard); - if (chessGame.isEndGameByPiece()) { - return chessGame.getWinner().name(); + public String selectWinner(int gameId) { + ChessBoard chessBoard = Square.toBoard((boardDao.findById(gameId))); + + if (chessBoard.isEnd()) { + return chessBoard.getWinner().name(); } return null; } - @Transactional - public void insertGame() { - ChessGame chessGame = new ChessGame(new NormalPiecesGenerator()); - chessGame.playGameByCommand(GameCommand.of("start")); + public String selectState(int gameId) { + Game game = gameDao.findById(gameId); + return game.getState(); + } - gameDao.delete(); - gameDao.save(chessGame.getState().toString()); - boardDao.save(chessGame.getChessBoard(), gameDao.findId()); + public StatusDto selectStatus(int gameId) { + Game game = gameDao.findById(gameId); + ChessBoard chessBoard = Square.toBoard((boardDao.findById(gameId))); + ChessGame chessGame = new ChessGame(State.of(game.getState()), chessBoard); + Map scores = chessGame.calculateScore(); + return new StatusDto(scores); } @Transactional - public void updateBoard(String from, String to) { - ChessGame chessGame = new ChessGame(gameDao.findState(), boardDao.find()); + public void movePiece(int gameId, String from, String to) { + Game game = gameDao.findById(gameId); + ChessBoard chessBoard = Square.toBoard((boardDao.findById(gameId))); + ChessGame chessGame = new ChessGame(State.of(game.getState()), chessBoard); + playChessGame(from, to, chessGame); + + gameDao.update(new Game(chessGame.getState().getValue(), gameId)); + boardDao.update(new Square(to, chessBoard.selectPiece(Position.of(to)), gameId)); + boardDao.update(new Square(from, EmptyPiece.getInstance(), gameId)); + } + + private void playChessGame(String from, String to, ChessGame chessGame) { chessGame.playGameByCommand(GameCommand.of("move", from, to)); chessGame.isEndGameByPiece(); - gameDao.update(chessGame.getState().toString(), gameDao.findId()); + } - Map pieces = chessGame.getChessBoard().toMap(); - boardDao.update(Position.of(to), pieces.get(to), gameDao.findId()); - boardDao.update(Position.of(from), EmptyPiece.getInstance(), gameDao.findId()); + @Transactional + public void endGame(int gameId) { + gameDao.update(new Game(StateName.FINISH.getValue(), gameId)); } - public StatusDto selectStatus() { - ChessGame chessGame = new ChessGame(gameDao.findState(), boardDao.find()); - chessGame.playGameByCommand(GameCommand.of("status")); - Map scores = chessGame.calculateScore(); + @Transactional + public void deleteGame(int gameId, String password) { + Game game = gameDao.findById(gameId); + State state = State.of(game.getState()); + validateState(state); + if (game.isSamePassword(password)) { + gameDao.delete(gameId); + return; + } + throw new IllegalStateException("비밀번호가 일치하지 않습니다."); + } - return new StatusDto(scores.get(Color.WHITE), scores.get(Color.BLACK)); + private void validateState(State state) { + if (!state.isFinished()) { + throw new IllegalStateException("진행중인 게임은 삭제할 수 없습니다."); + } } @Transactional - public void deleteGame() { - ChessGame chessGame = new ChessGame(gameDao.findState(), boardDao.find()); - - chessGame.playGameByCommand(GameCommand.of("end")); - gameDao.delete(); + public void restartGame(int gameId) { + ChessBoard chessBoard = new ChessBoard(new NormalPiecesGenerator()); + int id = gameDao.update(new Game(StateName.WHITE_TURN.getValue(), gameId)); + boardDao.delete(gameId); + boardDao.save(Square.from(chessBoard, id)); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3806e3738c..8cd130e6cf 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,11 +1,3 @@ -spring: - profiles: - group: - "default": "default" - "test": "test" - ---- - spring: config: activate: @@ -17,18 +9,11 @@ spring: username: root password: root +# sql: +# init: +# mode: always + mvc: hiddenmethod: filter: enabled: true - ---- - -spring: - config: - activate: - on-profile: "test" - - h2: - console: - enabled: true diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000000..db0c9c0d44 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,20 @@ +CREATE TABLE game +( + id int auto_increment + primary key, + title varchar(20) not null, + password varchar(20) not null, + state varchar(20) not null +); + +CREATE TABLE board +( + position varchar(5) not null, + symbol varchar(10) not null, + color varchar(10) not null, + game_id int not null, + primary key (position, game_id), + constraint board_game_id_fk + foreign key (game_id) references game (id) + on update cascade on delete cascade +) diff --git a/src/main/resources/static/src/game.css b/src/main/resources/static/src/game.css new file mode 100644 index 0000000000..06e9472516 --- /dev/null +++ b/src/main/resources/static/src/game.css @@ -0,0 +1,37 @@ +body { + background-color: aquamarine; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.chess-ui { + width: 640px; + border: 4px solid slategrey; + display: grid; + grid-template-columns: repeat(8, 80px); + grid-template-rows: repeat(8, 80px); +} + +.white-square { + background-color: slategrey; + color: white; +} + +.white-square.selected { + background-color: rosybrown; +} + +.black-square { + background-color: white; + color: slategrey; +} + +.black-square.selected { + background-color: rosybrown; +} + +img { + width: 100%; +} diff --git a/src/main/resources/static/src/index.js b/src/main/resources/static/src/game.js similarity index 75% rename from src/main/resources/static/src/index.js rename to src/main/resources/static/src/game.js index c5e81d0f86..7558cc01f9 100644 --- a/src/main/resources/static/src/index.js +++ b/src/main/resources/static/src/game.js @@ -1,8 +1,7 @@ - const score = document.getElementById("score"); -async function selectStatus() { - const res = await fetch('/game/status', { +async function selectStatus(id) { + const res = await fetch('/game/status/' + id, { method: 'get' }); diff --git a/src/main/resources/static/src/index.css b/src/main/resources/static/src/index.css index 0e61e4e7ee..ec6b7b219b 100644 --- a/src/main/resources/static/src/index.css +++ b/src/main/resources/static/src/index.css @@ -1,29 +1,13 @@ -.chess-ui { - width: 640px; - border: 4px solid slategrey; - display: grid; - grid-template-columns: repeat(8, 80px); - grid-template-rows: repeat(8, 80px); -} - -.white-square { - background-color: slategrey; - color: white; -} - -.white-square.selected { - background-color: rosybrown; -} - -.black-square { - background-color: white; - color: slategrey; -} - -.black-square.selected { - background-color: rosybrown; -} - -img { - width: 100%; +body { + background-color: aquamarine; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +a { + font-size: 30px; + color: grey; + text-decoration: none; } diff --git a/src/main/resources/templates/game.hbs b/src/main/resources/templates/game.hbs new file mode 100644 index 0000000000..8e6f53c2c2 --- /dev/null +++ b/src/main/resources/templates/game.hbs @@ -0,0 +1,113 @@ + + + + + 체스 게임 + + + +

체스게임

+
+
+ {{#this}} + {{#board}} +
{{#board.a8}}{{/board.a8}}
+
{{#board.b8}}{{/board.b8}}
+
{{#board.c8}}{{/board.c8}}
+
{{#board.d8}}{{/board.d8}}
+
{{#board.e8}}{{/board.e8}}
+
{{#board.f8}}{{/board.f8}}
+
{{#board.g8}}{{/board.g8}}
+
{{#board.h8}}{{/board.h8}}
+ +
{{#board.a7}}{{/board.a7}}
+
{{#board.b7}}{{/board.b7}}
+
{{#board.c7}}{{/board.c7}}
+
{{#board.d7}}{{/board.d7}}
+
{{#board.e7}}{{/board.e7}}
+
{{#board.f7}}{{/board.f7}}
+
{{#board.g7}}{{/board.g7}}
+
{{#board.h7}}{{/board.h7}}
+ +
{{#board.a6}}{{/board.a6}}
+
{{#board.b6}}{{/board.b6}}
+
{{#board.c6}}{{/board.c6}}
+
{{#board.d6}}{{/board.d6}}
+
{{#board.e6}}{{/board.e6}}
+
{{#board.f6}}{{/board.f6}}
+
{{#board.g6}}{{/board.g6}}
+
{{#board.h6}}{{/board.h6}}
+ +
{{#board.a5}}{{/board.a5}}
+
{{#board.b5}}{{/board.b5}}
+
{{#board.c5}}{{/board.c5}}
+
{{#board.d5}}{{/board.d5}}
+
{{#board.e5}}{{/board.e5}}
+
{{#board.f5}}{{/board.f5}}
+
{{#board.g5}}{{/board.g5}}
+
{{#board.h5}}{{/board.h5}}
+ +
{{#board.a4}}{{/board.a4}}
+
{{#board.b4}}{{/board.b4}}
+
{{#board.c4}}{{/board.c4}}
+
{{#board.d4}}{{/board.d4}}
+
{{#board.e4}}{{/board.e4}}
+
{{#board.f4}}{{/board.f4}}
+
{{#board.g4}}{{/board.g4}}
+
{{#board.h4}}{{/board.h4}}
+ +
{{#board.a3}}{{/board.a3}}
+
{{#board.b3}}{{/board.b3}}
+
{{#board.c3}}{{/board.c3}}
+
{{#board.d3}}{{/board.d3}}
+
{{#board.e3}}{{/board.e3}}
+
{{#board.f3}}{{/board.f3}}
+
{{#board.g3}}{{/board.g3}}
+
{{#board.h3}}{{/board.h3}}
+ +
{{#board.a2}}{{/board.a2}}
+
{{#board.b2}}{{/board.b2}}
+
{{#board.c2}}{{/board.c2}}
+
{{#board.d2}}{{/board.d2}}
+
{{#board.e2}}{{/board.e2}}
+
{{#board.f2}}{{/board.f2}}
+
{{#board.g2}}{{/board.g2}}
+
{{#board.h2}}{{/board.h2}}
+ +
{{#board.a1}}{{/board.a1}}
+
{{#board.b1}}{{/board.b1}}
+
{{#board.c1}}{{/board.c1}}
+
{{#board.d1}}{{/board.d1}}
+
{{#board.e1}}{{/board.e1}}
+
{{#board.f1}}{{/board.f1}}
+
{{#board.g1}}{{/board.g1}}
+
{{#board.h1}}{{/board.h1}}
+ {{/board}} + {{/this}} +
+

차례 : {{state}}

+

{{#if winner}} {{winner}} 승리 {{/if}}

+ +
+ + +
+ +
+ + +
+ +
+ + + + +
+ + +

{{data}}

+
+ + + diff --git a/src/main/resources/templates/index.hbs b/src/main/resources/templates/index.hbs index 64425b68e2..fe46f935ae 100644 --- a/src/main/resources/templates/index.hbs +++ b/src/main/resources/templates/index.hbs @@ -6,105 +6,36 @@ -

체스게임

-
-
- {{#this}} - {{#board}} -
{{#board.a8}}{{/board.a8}}
-
{{#board.b8}}{{/board.b8}}
-
{{#board.c8}}{{/board.c8}}
-
{{#board.d8}}{{/board.d8}}
-
{{#board.e8}}{{/board.e8}}
-
{{#board.f8}}{{/board.f8}}
-
{{#board.g8}}{{/board.g8}}
-
{{#board.h8}}{{/board.h8}}
- -
{{#board.a7}}{{/board.a7}}
-
{{#board.b7}}{{/board.b7}}
-
{{#board.c7}}{{/board.c7}}
-
{{#board.d7}}{{/board.d7}}
-
{{#board.e7}}{{/board.e7}}
-
{{#board.f7}}{{/board.f7}}
-
{{#board.g7}}{{/board.g7}}
-
{{#board.h7}}{{/board.h7}}
- -
{{#board.a6}}{{/board.a6}}
-
{{#board.b6}}{{/board.b6}}
-
{{#board.c6}}{{/board.c6}}
-
{{#board.d6}}{{/board.d6}}
-
{{#board.e6}}{{/board.e6}}
-
{{#board.f6}}{{/board.f6}}
-
{{#board.g6}}{{/board.g6}}
-
{{#board.h6}}{{/board.h6}}
- -
{{#board.a5}}{{/board.a5}}
-
{{#board.b5}}{{/board.b5}}
-
{{#board.c5}}{{/board.c5}}
-
{{#board.d5}}{{/board.d5}}
-
{{#board.e5}}{{/board.e5}}
-
{{#board.f5}}{{/board.f5}}
-
{{#board.g5}}{{/board.g5}}
-
{{#board.h5}}{{/board.h5}}
- -
{{#board.a4}}{{/board.a4}}
-
{{#board.b4}}{{/board.b4}}
-
{{#board.c4}}{{/board.c4}}
-
{{#board.d4}}{{/board.d4}}
-
{{#board.e4}}{{/board.e4}}
-
{{#board.f4}}{{/board.f4}}
-
{{#board.g4}}{{/board.g4}}
-
{{#board.h4}}{{/board.h4}}
- -
{{#board.a3}}{{/board.a3}}
-
{{#board.b3}}{{/board.b3}}
-
{{#board.c3}}{{/board.c3}}
-
{{#board.d3}}{{/board.d3}}
-
{{#board.e3}}{{/board.e3}}
-
{{#board.f3}}{{/board.f3}}
-
{{#board.g3}}{{/board.g3}}
-
{{#board.h3}}{{/board.h3}}
- -
{{#board.a2}}{{/board.a2}}
-
{{#board.b2}}{{/board.b2}}
-
{{#board.c2}}{{/board.c2}}
-
{{#board.d2}}{{/board.d2}}
-
{{#board.e2}}{{/board.e2}}
-
{{#board.f2}}{{/board.f2}}
-
{{#board.g2}}{{/board.g2}}
-
{{#board.h2}}{{/board.h2}}
- -
{{#board.a1}}{{/board.a1}}
-
{{#board.b1}}{{/board.b1}}
-
{{#board.c1}}{{/board.c1}}
-
{{#board.d1}}{{/board.d1}}
-
{{#board.e1}}{{/board.e1}}
-
{{#board.f1}}{{/board.f1}}
-
{{#board.g1}}{{/board.g1}}
-
{{#board.h1}}{{/board.h1}}
- {{/board}} - {{/this}} -
-

{{#if winner}} {{winner}} 승리 {{/if}}

-
- -
- -
- - -
- -
- - - - -
- - -

{{data}}

-
+

체스게임

+
+
+ + 방 이름 + + + + 비밀번호 + + + +
+
+
+
+ {{#each game}} + +
+ {{/each}} +
- diff --git a/src/test/java/chess/dao/BoardDaoTest.java b/src/test/java/chess/dao/BoardDaoTest.java new file mode 100644 index 0000000000..73b66093e6 --- /dev/null +++ b/src/test/java/chess/dao/BoardDaoTest.java @@ -0,0 +1,66 @@ +package chess.dao; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.chessboard.ChessBoard; +import chess.domain.piece.Symbol; +import chess.domain.piece.generator.NormalPiecesGenerator; +import chess.entity.Game; +import chess.entity.Square; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; + +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +public class BoardDaoTest { + + @Autowired + private BoardDao boardDao; + + @Autowired + private GameDao gameDao; + + private int gameId; + + @BeforeEach + void setUp() { + gameId = gameDao.save(new Game("title", "password", "WhiteTurn")); + boardDao.save(Square.from(new ChessBoard(new NormalPiecesGenerator()), gameId)); + } + + @Test + @DisplayName("체스 보드를 조회할 수 있다.") + void findById() { + var squares = boardDao.findById(gameId); + + assertThat(squares.get(0)).isInstanceOf(Square.class); + } + + @Test + @DisplayName("체스 보드를 업데이트할 수 있다.") + void update() { + Square square = new Square("a1", "PAWN", "WHITE", gameId); + + boardDao.update(square); + List squares = boardDao.findById(gameId); + Square actual = squares.stream() + .filter(it -> it.getPosition().equals("a1")) + .findAny() + .orElseThrow(IllegalArgumentException::new); + + assertThat(actual.getSymbol()).isEqualTo(Symbol.PAWN.name()); + } + + @Test + @DisplayName("체스 보드를 삭제할 수 있다.") + void delete() { + boardDao.delete(gameId); + + List squares = boardDao.findById(gameId); + assertThat(squares).isEmpty(); + } +} diff --git a/src/test/java/chess/dao/FakeBoardDao.java b/src/test/java/chess/dao/FakeBoardDao.java new file mode 100644 index 0000000000..6b0d5ffcb6 --- /dev/null +++ b/src/test/java/chess/dao/FakeBoardDao.java @@ -0,0 +1,44 @@ +package chess.dao; + +import chess.entity.Square; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class FakeBoardDao implements BoardDao { + + private final List board; + + public FakeBoardDao() { + this.board = new ArrayList<>(); + } + + @Override + public void save(List squares) { + this.board.addAll(squares); + } + + @Override + public List findById(int id) { + return board.stream() + .filter(square -> square.getGameId() == id) + .collect(Collectors.toList()); + } + + @Override + public int update(Square square) { + Square square1 = board.stream() + .filter(it -> it.getGameId() == square.getGameId() && it.getPosition().equals(square.getPosition())) + .findAny() + .orElseThrow(IllegalArgumentException::new); + board.remove(square1); + + board.add(square); + return square.getGameId(); + } + + @Override + public void delete(int gameId) { + board.remove(gameId); + } +} diff --git a/src/test/java/chess/dao/FakeGameDao.java b/src/test/java/chess/dao/FakeGameDao.java new file mode 100644 index 0000000000..3dd0018b10 --- /dev/null +++ b/src/test/java/chess/dao/FakeGameDao.java @@ -0,0 +1,47 @@ +package chess.dao; + +import chess.domain.state.State; +import chess.entity.Game; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FakeGameDao implements GameDao { + + private final Map game; + private int id = 1; + + public FakeGameDao() { + game = new HashMap<>(); + } + + @Override + public int save(Game game) { + this.game.put(id, game); + return id++; + } + + @Override + public List findAll() { + return new ArrayList<>(game.values()); + } + + @Override + public Game findById(int id) { + return game.get(id); + } + + @Override + public int update(Game game) { + Game oldGame = this.game.get(game.getId()); + this.game.put(game.getId(), new Game(game.getId(), oldGame.getTitle(), oldGame.getPassword(), game.getState())); + return game.getId(); + } + + @Override + public int delete(int id) { + game.remove(id); + return id; + } +} diff --git a/src/test/java/chess/dao/GameDaoTest.java b/src/test/java/chess/dao/GameDaoTest.java new file mode 100644 index 0000000000..d639cdaaa3 --- /dev/null +++ b/src/test/java/chess/dao/GameDaoTest.java @@ -0,0 +1,58 @@ +package chess.dao; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.entity.Game; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; + +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +class GameDaoTest { + + @Autowired + private GameDao gameDao; + + private int gameId; + + @BeforeEach + void setUp() { + gameId = gameDao.save(new Game("title", "password", "WhiteTurn")); + } + + @AfterEach + void clear() { + gameDao.delete(gameId); + } + + @Test + @DisplayName("게임들의 리스트를 조회할 수 있다.") + void findAll() { + List games = gameDao.findAll(); + + assertThat(games.get(0).getTitle()).isEqualTo("title"); + } + + @Test + @DisplayName("게임을 조회할 수 있다.") + void findById() { + Game game = gameDao.findById(gameId); + + assertThat(game.getPassword()).isEqualTo("password"); + } + + @Test + @DisplayName("게임의 상태를 업데이트할 수 있다.") + void update() { + gameDao.update(new Game("BlackTurn", gameId)); + Game game = gameDao.findById(gameId); + String state = game.getState(); + + assertThat(state).isEqualTo("BlackTurn"); + } +} diff --git a/src/test/java/chess/repository/BoardDaoTest.java b/src/test/java/chess/repository/BoardDaoTest.java deleted file mode 100644 index c4633c0db6..0000000000 --- a/src/test/java/chess/repository/BoardDaoTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package chess.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import chess.domain.chessboard.ChessBoard; -import chess.domain.piece.Color; -import chess.domain.piece.Piece; -import chess.domain.piece.Symbol; -import chess.domain.piece.generator.NormalPiecesGenerator; -import chess.domain.position.Position; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.ActiveProfiles; - -@ActiveProfiles("test") -@SpringBootTest(webEnvironment = WebEnvironment.NONE) -public class BoardDaoTest { - - @Autowired - private BoardDao boardDao; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @BeforeEach - void setUp() { - jdbcTemplate.execute("DROP TABLE board IF EXISTS"); - jdbcTemplate.execute("DROP TABLE game IF EXISTS"); - jdbcTemplate.execute("create table game\n" - + "(\n" - + " id int auto_increment\n" - + " primary key,\n" - + " state varchar(20) not null\n" - + ");"); - jdbcTemplate.execute("create table board\n" - + "(\n" - + " position varchar(5) not null,\n" - + " symbol varchar(10) not null,\n" - + " color varchar(10) not null,\n" - + " game_id int not null,\n" - + " primary key (position, game_id),\n" - + " constraint board_game_id_fk\n" - + " foreign key (game_id) references game (id)\n" - + " on update cascade on delete cascade\n" - + ");"); - jdbcTemplate.update("insert into game(state) values (?)", "WhiteRunning"); - } - - @Test - @DisplayName("체스 보드를 생성할 수 있다.") - void save() { - ChessBoard chessBoard = new ChessBoard(new NormalPiecesGenerator()); - - assertThatCode(() -> - boardDao.save(chessBoard, 1L)).doesNotThrowAnyException(); - } - - @Test - @DisplayName("체스 보드를 조회할 수 있다") - void find() { - boardDao.save(new ChessBoard(new NormalPiecesGenerator()), 1L); - - assertThat(boardDao.find()).isInstanceOf(ChessBoard.class); - } - - @Test - @DisplayName("체스 보드를 갱신할 수 있다.") - void update() { - boardDao.save(new ChessBoard(new NormalPiecesGenerator()), 1L); - Position position = Position.of("a2"); - Piece piece = Piece.of(Color.WHITE, Symbol.PAWN); - - assertThatCode(() -> - boardDao.update(position, piece, 1L) - ).doesNotThrowAnyException(); - } -} diff --git a/src/test/java/chess/repository/GameDaoTest.java b/src/test/java/chess/repository/GameDaoTest.java deleted file mode 100644 index a8ae0addce..0000000000 --- a/src/test/java/chess/repository/GameDaoTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package chess.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import chess.domain.state.BlackRunning; -import chess.domain.state.State; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.ActiveProfiles; - -@ActiveProfiles("test") -@SpringBootTest(webEnvironment = WebEnvironment.NONE) -class GameDaoTest { - - @Autowired - private GameDao gameDao; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @BeforeEach - void setUp() { - jdbcTemplate.execute("DROP TABLE board IF EXISTS"); - jdbcTemplate.execute("DROP TABLE game IF EXISTS"); - jdbcTemplate.execute("create table game\n" - + "(\n" - + " id int auto_increment\n" - + " primary key,\n" - + " state varchar(20) not null\n" - + ");"); - jdbcTemplate.execute("create table board\n" - + "(\n" - + " position varchar(5) not null,\n" - + " symbol varchar(10) not null,\n" - + " color varchar(10) not null,\n" - + " game_id int not null,\n" - + " primary key (position, game_id),\n" - + " constraint board_game_id_fk\n" - + " foreign key (game_id) references game (id)\n" - + " on update cascade on delete cascade\n" - + ");"); - jdbcTemplate.update("insert into game(state) values (?)", "WhiteRunning"); - } - - @Test - @DisplayName("게임을 생성할 수 있다.") - void saveGame() { - assertThatCode(() -> { - gameDao.save("WhiteRunning"); - }).doesNotThrowAnyException(); - } - - @Test - @DisplayName("게임의 상태를 조회할 수 있다.") - void findState() { - assertThat(gameDao.findState()).isInstanceOf(State.class); - } - - @Test - @DisplayName("게임의 일련번호를 조회할 수 있다.") - void findId() { - assertThat(gameDao.findId()).isEqualTo(1L); - } - - @Test - @DisplayName("게임의 상태를 업데이트할 수 있다.") - void updateGameByState() { - gameDao.update("BlackRunning", 1L); - - assertThat(gameDao.findState()).isInstanceOf(BlackRunning.class); - } - - @Test - @DisplayName("게임을 제거할 수 있다.") - void deleteGame() { - assertThat(gameDao.delete()).isEqualTo(1L); - } -} diff --git a/src/test/java/chess/service/ChessServiceTest.java b/src/test/java/chess/service/ChessServiceTest.java new file mode 100644 index 0000000000..28fa06bd9c --- /dev/null +++ b/src/test/java/chess/service/ChessServiceTest.java @@ -0,0 +1,171 @@ +package chess.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.dao.BoardDao; +import chess.dao.FakeBoardDao; +import chess.dao.FakeGameDao; +import chess.dao.GameDao; +import chess.domain.chessboard.ChessBoard; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.Symbol; +import chess.domain.piece.generator.NormalPiecesGenerator; +import chess.domain.piece.generator.PiecesGenerator; +import chess.domain.position.Position; +import chess.dto.BoardDto; +import chess.dto.GameDto; +import chess.dto.PieceDto; +import chess.dto.RoomDto; +import chess.dto.StatusDto; +import chess.entity.Game; +import chess.entity.Square; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ChessServiceTest { + + private GameDao gameDao = new FakeGameDao(); + private BoardDao boardDao = new FakeBoardDao(); + private int id; + + private ChessService chessService = new ChessService(gameDao, boardDao); + + @BeforeEach + void setUp() { + RoomDto roomDto = new RoomDto("title", "password"); + id = chessService.insertGame(roomDto, new ChessBoard(new NormalPiecesGenerator())); + } + + @AfterEach + void clear() { + gameDao.delete(id); + boardDao.delete(id); + } + + @Test + @DisplayName("게임들의 리스트를 반환한다.") + void findGame() { + List game = chessService.findGame(); + String password = game.get(id - 1).getTitle(); + + assertThat(password).isEqualTo("title"); + } + + @Test + @DisplayName("BoardDto를 반환한다.") + void selectBoard() { + BoardDto boardDto = chessService.selectBoard(id); + Map board = boardDto.getBoard(); + Piece piece = createPiece(board.get("a2")); + + assertThat(piece).isEqualTo(Piece.of(Color.WHITE, Symbol.PAWN)); + } + + private Piece createPiece(PieceDto pieceDto) { + return Piece.of(Color.valueOf(pieceDto.getColor()), Symbol.valueOf(pieceDto.getSymbol())); + } + + @Test + @DisplayName("승자를 불러올 수 있다") + void selectWinner() { + RoomDto roomDto = new RoomDto("title", "password"); + id = chessService.insertGame(roomDto, createTestBoard()); + String winner = chessService.selectWinner(id); + + assertThat(winner).isEqualTo("WHITE"); + } + + private ChessBoard createTestBoard() { + final Map testPieces = new HashMap<>(Map.ofEntries( + Map.entry(Position.of("a1"), Piece.of(Color.WHITE, Symbol.KING)), + Map.entry(Position.of("b3"), Piece.of(Color.WHITE, Symbol.PAWN)), + Map.entry(Position.of("c4"), Piece.of(Color.WHITE, Symbol.PAWN)), + Map.entry(Position.of("a7"), Piece.of(Color.BLACK, Symbol.PAWN)), + Map.entry(Position.of("c5"), Piece.of(Color.BLACK, Symbol.PAWN)) + )); + PiecesGenerator.fillEmptyPiece(testPieces); + return new ChessBoard(() -> testPieces); + } + + @Test + @DisplayName("게임의 상태 불러올 수 있다.") + void selectState() { + String state = chessService.selectState(id); + + assertThat(state).isEqualTo("WhiteTurn"); + } + + @Test + @DisplayName("게임의 점수를 불러올 수 있다.") + void selectStatus() { + StatusDto statusDto = chessService.selectStatus(id); + + assertThat(statusDto.getWhiteScore()).isEqualTo("38.0"); + } + + @Test + @DisplayName("보드에서 말을 움직일 수 있다.") + void movePiece() { + chessService.movePiece(id, "a2", "a3"); + ChessBoard chessBoard = Square.toBoard(boardDao.findById(id)); + Piece piece = chessBoard.selectPiece(Position.of("a3")); + + assertThat(piece).isEqualTo(Piece.of(Color.WHITE, Symbol.PAWN)); + } + + @Test + @DisplayName("게임을 종료시킬 수 있다.") + void endGame() { + chessService.endGame(id); + Game game = gameDao.findById(id); + String state = game.getState(); + + assertThat(state).isEqualTo("Finish"); + } + + @Test + @DisplayName("종료되지 않은 게임은 삭제할 수 없다.") + void deleteGameThrownException() { + assertThatThrownBy(() -> + chessService.deleteGame(id, "password")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("진행중인 게임은 삭제할 수 없습니다."); + } + + @Test + @DisplayName("비밀번호가 다르면 게임을 삭제할 수 없다.") + void deleteGameThrownExceptionWithPassword() { + chessService.endGame(id); + assertThatThrownBy(() -> + chessService.deleteGame(id, "passwo")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("비밀번호가 일치하지 않습니다."); + } + + @Test + @DisplayName("비밀번호가 일치하면 게임을 삭제할 수 있다.") + void deleteGame() { + chessService.endGame(id); + chessService.deleteGame(id, "password"); + + assertThat(gameDao.findAll()).hasSize(0); + } + + @Test + @DisplayName("게임을 재시작할 수 있다.") + void restartGame() { + chessService.endGame(id); + chessService.restartGame(id); + Game game = gameDao.findById(id); + String state = game.getState(); + + assertThat(state).isEqualTo("WhiteTurn"); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000000..f7e703c7bb --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,14 @@ +spring: + config: + activate: + on-profile: "test" + + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=MySQL + username: sa + password: + + h2: + console: + enabled: true diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000000..3ab28c339c --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS board; +DROP TABLE IF EXISTS game; + +CREATE TABLE game +( + id int auto_increment + primary key, + title varchar(20) not null, + password varchar(20) not null, + state varchar(20) not null +); + +CREATE TABLE board +( + position varchar(5) not null, + symbol varchar(10) not null, + color varchar(10) not null, + game_id int not null, + primary key (position, game_id), + constraint board_game_id_fk + foreign key (game_id) references game (id) + on update cascade on delete cascade +);