Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/16/backend flow game start #35

Merged
merged 9 commits into from
Sep 15, 2023
25 changes: 24 additions & 1 deletion backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<properties>
<java.version>17</java.version>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.26</org.projectlombok.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -44,6 +45,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
Expand All @@ -56,17 +58,28 @@
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
<version>2.2.220</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down Expand Up @@ -108,6 +121,16 @@
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
package tw.waterballsa.gaas.unoflip.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import tw.waterballsa.gaas.unoflip.presenter.GameJoinPresenter;
import tw.waterballsa.gaas.unoflip.service.SseService;
import tw.waterballsa.gaas.unoflip.usecase.GameJoinUseCase;
import tw.waterballsa.gaas.unoflip.vo.GameJoinResult;
import tw.waterballsa.gaas.unoflip.vo.JoinRequest;
import tw.waterballsa.gaas.unoflip.vo.JoinResult;
import tw.waterballsa.gaas.unoflip.vo.Response;
import tw.waterballsa.gaas.unoflip.response.JoinResult;
import tw.waterballsa.gaas.unoflip.response.Response;

@RestController
@RequiredArgsConstructor
public class GameController {

private final GameJoinUseCase gameJoinUseCase;
private final GameJoinPresenter gameJoinPresenter;
private final SseService sseService;

public GameController(GameJoinUseCase gameJoinUseCase, GameJoinPresenter gameJoinPresenter, SseService sseService) {
this.gameJoinUseCase = gameJoinUseCase;
this.gameJoinPresenter = gameJoinPresenter;
this.sseService = sseService;
}

@PostMapping("join/{playerId}")
public Response<JoinResult> join(@PathVariable String playerId, @RequestBody JoinRequest joinRequest) {
GameJoinResult gameJoinResult = gameJoinUseCase.join(playerId, joinRequest.playerName());
sseService.sendMessage(gameJoinPresenter.broadcastEvent(playerId, gameJoinResult));
return gameJoinPresenter.response(playerId, gameJoinResult);
return gameJoinUseCase.join(playerId, joinRequest.playerName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tw.waterballsa.gaas.unoflip.domain;

import tw.waterballsa.gaas.unoflip.domain.eumns.Card;

import java.util.List;

record DealResult(List<HandCard> playersHandCard, Card discardCard, List<Card> drawPileCards) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tw.waterballsa.gaas.unoflip.domain;

import tw.waterballsa.gaas.unoflip.domain.eumns.Card;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

class Dealer {
public static DealResult deal() {
List<Integer> cardNumbers = Card.getAllIds();
Collections.shuffle(cardNumbers);

List<HandCard> playersHandCards = new ArrayList<>();

playersHandCards.add(createHandCard(0, 7, cardNumbers));
playersHandCards.add(createHandCard(7, 14, cardNumbers));
playersHandCards.add(createHandCard(14, 21, cardNumbers));
playersHandCards.add(createHandCard(21, 28, cardNumbers));

Card discardCard = Card.getLightInstance(cardNumbers.get(28));

List<Card> drawPileCards = getRandomCardList(29, 112, cardNumbers);

return new DealResult(playersHandCards, discardCard, drawPileCards);
}

private static HandCard createHandCard(int startInclusive, int endExclusive, List<Integer> cardNumbers) {
return new HandCard(getRandomCardList(startInclusive, endExclusive, cardNumbers));
}

private static List<Card> getRandomCardList(int startInclusive, int endExclusive, List<Integer> cardNumbers) {
return IntStream.range(startInclusive, endExclusive).mapToObj(i -> Card.getLightInstance(cardNumbers.get(i))).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tw.waterballsa.gaas.unoflip.domain;

import tw.waterballsa.gaas.unoflip.domain.eumns.Card;

import java.util.Collections;
import java.util.List;

public class HandCard {
private final List<Card> cards;

public HandCard(List<Card> cards) {
this.cards = cards;
}

public List<Integer> toCardIds() {
return cards.stream().map(Card::getId).toList();
}

int size() {
return cards.size();
}

List<Card> getCards() {
return Collections.unmodifiableList(cards);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tw.waterballsa.gaas.unoflip.domain;

import lombok.Getter;
import lombok.Setter;

@Getter
public class Player {

private final PlayerInfo playerInfo;
@Setter
private HandCard handCard;

public Player(PlayerInfo playerInfo) {
this.playerInfo = playerInfo;
}

public int getPosition() {
return playerInfo.position();
}

public String getId() {
return playerInfo.id();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package tw.waterballsa.gaas.unoflip.domain;

public record PlayerInfo(String id, String name, Integer position) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tw.waterballsa.gaas.unoflip.domain;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class Players {
private final Map<String, Player> playerMap = new HashMap<>();

public boolean exists(String playerId) {
return playerMap.get(playerId) != null;
}

public void add(PlayerInfo playerInfo) {
playerMap.put(playerInfo.id(), new Player(playerInfo));
}

public List<PlayerInfo> toInfoList() {
return playerMap.values().stream().map(Player::getPlayerInfo).toList();
}

public HandCard getPlayerHandCard(String playerId) {
return Optional.ofNullable(playerMap.get(playerId))
.map(Player::getHandCard)
.orElseThrow(() -> new IllegalArgumentException("player %s not exists".formatted(playerId)));
}

public void setHandCard(String playerId, HandCard handCard) {
Player player = Optional.ofNullable(playerMap.get(playerId)).orElseThrow(() -> new IllegalArgumentException("player %s not exists".formatted(playerId)));
player.setHandCard(handCard);
}

public int size() {
return playerMap.size();
}

public List<String> getIds() {
return playerMap.values().stream().map(Player::getId).toList();
}

public String getPlayerId(int position) {
return playerMap.values().stream()
.filter(player -> position == player.getPosition())
.findFirst()
.map(Player::getId)
.orElseThrow(() -> new IllegalArgumentException("position %d not exists".formatted(position)));
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,96 @@
package tw.waterballsa.gaas.unoflip.domain;

import tw.waterballsa.gaas.unoflip.vo.PlayerInfo;
import lombok.Getter;
import tw.waterballsa.gaas.unoflip.domain.eumns.Card;
import tw.waterballsa.gaas.unoflip.domain.eumns.Direction;
import tw.waterballsa.gaas.unoflip.domain.eumns.GameMode;
import tw.waterballsa.gaas.unoflip.domain.eumns.GameStatus;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class UnoFlipGame {
private static final int MAX_PLAYER_NUMBER = 4;
private final List<Card> drawPileList = new ArrayList<>();
private final List<Card> discardPileList = new ArrayList<>();

@Getter
private final Players players = new Players();
@Getter
private final int tableId;
private final List<PlayerInfo> playerInfoList = new ArrayList<>();
@Getter
private String actionPlayerId;
@Getter
private GameStatus status;
@Getter
private Direction direction;
@Getter
private GameMode mode;

public UnoFlipGame(int tableId) {
this.tableId = tableId;
this.status = GameStatus.WAITING;
this.direction = Direction.RIGHT;
this.mode = GameMode.LIGHT;
}

public int getTableId() {
return tableId;
public boolean isFull() {
return players.size() >= MAX_PLAYER_NUMBER;
}

public void join(String playerId, String playerName) {
if (isPlayerAlreadyInGame(playerId)) {
throw new RuntimeException("player already in game");
}
public List<PlayerInfo> getPlayerInfoList() {
return players.toInfoList();
}

playerInfoList.add(new PlayerInfo(playerId, playerName, getAvailablePosition()));
public List<Card> getDrawPile() {
return Collections.unmodifiableList(drawPileList);
}

public List<PlayerInfo> getPlayerInfoList() {
return Collections.unmodifiableList(playerInfoList);
public List<Card> getDiscardPile() {
return Collections.unmodifiableList(discardPileList);
}

public boolean isFull() {
return playerInfoList.size() >= MAX_PLAYER_NUMBER;
public void join(String playerId, String playerName) {
if (isPlayerAlreadyInGame(playerId)) {
throw new RuntimeException("player already in game");
}

players.add(new PlayerInfo(playerId, playerName, getAvailablePosition()));
}

private boolean isPlayerAlreadyInGame(String playerId) {
return playerInfoList.stream().anyMatch(playerInfo -> playerId.equals(playerInfo.playerId()));
return players.exists(playerId);
}

private int getAvailablePosition() {
if (isFull()) {
throw new RuntimeException("game is full");
throw new IllegalStateException("game is full");
}

return playerInfoList.size() + 1;
return players.size() + 1;
}

public void start() {
status = GameStatus.STARTED;
actionPlayerId = players.getPlayerId(getInitPosition());

DealResult dealResult = Dealer.deal();

setPlayersHandCard(dealResult);
discardPileList.add(dealResult.discardCard());
drawPileList.addAll(dealResult.drawPileCards());
}

private int getInitPosition() {
return (int) (Math.random() * MAX_PLAYER_NUMBER) + 1;
}

private void setPlayersHandCard(DealResult dealResult) {
int handCardListIdx = 0;
for (String playerId : players.getIds()) {
players.setHandCard(playerId, dealResult.playersHandCard().get(handCardListIdx++));
}
}

}
Loading