Skip to content

Commit

Permalink
[둘리] 1, 2단계 오목 제출합니다. (woowacourse#26)
Browse files Browse the repository at this point in the history
* docs: 오목 기능 목록 작성

* feat: 1부터 15 위치를 가진 오목알 클래스 구현

* feat: 중복 오목알 처리를 위한 오목알 일급 컬렉션 구현

* docs: .gitkeep 파일 제거

* feat: 오목알을 놓은 플레이어가 게임에서 이겼는지 확인하는 기능 구현

* feat: 사용자가 오목알을 놓았을 때 상태를 반환하는 기능 구현

* feat: 오목판에 돌을 올려놓는 기능 구현

* feat: 플레이어의 오목알을 놓는 턴을 바꾸는 기능 구현

* refactor: 돌을 놓으면 새로운 상태를 가진 플레이어 반환하는 기능 구현

* feat: 플레이어가 놓은 마지막 돌을 리턴하는 기능 구현

* refactor: 오목알을 놓을 수 있는지 확인하는 함수를 Players 클래스로 이동

* feat: 오목 게임 진행하는 기능 구현

* feat: 오목 게임 입력, 출력 화면 구현

* feat: 오목 컨트롤러 구현

* refactor: 도메인 패키지 분리

* fix: 중간에 오목알을 뒀을 때 승리 판정하지 않는 오류 수정

* feat: 렌주룰 기능 추가 (미완성)

* feat: 렌주룰 적용 (완성)

---------

Co-authored-by: tmdgh1592 <[email protected]>
  • Loading branch information
hyemdooly and tmdgh1592 committed Mar 24, 2023
1 parent 8bedfaa commit 21d97d3
Show file tree
Hide file tree
Showing 36 changed files with 1,046 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 오목

### Domain

- [x] 한 플레이어라도 승리할 때까지 차례를 번갈아가면서 돌을 놓는다.
- [x] 흑, 백 플레이어를 가지고 있다
- [x] 흑돌이 먼저 시작한다.
Expand All @@ -24,4 +25,4 @@

### Output
- [x] 오목알의 위치를 입력받기 전에 오목판을 출력한다.
- [x] 마지막 돌의 위치를 출력한다.
- [x] 마지막 돌의 위치를 출력한다.
5 changes: 5 additions & 0 deletions src/main/kotlin/Application.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import controller.OmokController

fun main() {
OmokController().start()
}
12 changes: 12 additions & 0 deletions src/main/kotlin/controller/OmokController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package controller

import domain.game.Omok
import domain.rule.RenjuRule
import view.InputView
import view.OutputView

class OmokController {
fun start() {
Omok(OutputView(), InputView(), RenjuRule()).run()
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/domain/board/Board.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain.board

import domain.player.Player
import domain.player.Players
import domain.rule.OmokRule
import domain.stone.Stone
import domain.stone.StoneColor

class Board(private val players: Players) {
constructor(blackPlayer: Player, whitePlayer: Player, rule: OmokRule) : this(Players(blackPlayer, whitePlayer, rule))

fun putStone(stoneColor: StoneColor, stone: Stone): Board? {
if (players.canPlace(stone)) {
return Board(players.putStone(stoneColor, stone))
}
return null
}

fun getPlayers(): Players = players.copy()

fun isRunning(): Boolean = players.isRunning

fun isLose(): Boolean = players.isBlackLose
}
39 changes: 39 additions & 0 deletions src/main/kotlin/domain/game/Omok.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package domain.game

import domain.board.Board
import domain.player.BlackPlayer
import domain.player.WhitePlayer
import domain.rule.OmokRule
import domain.stone.Stone
import domain.stone.StoneColor
import listener.OmokStartEndEventListener
import listener.OmokTurnEventListener

class Omok(
private val startEndEventListener: OmokStartEndEventListener,
private val turnEventListener: OmokTurnEventListener,
private val rule: OmokRule
) {
fun run() {
startEndEventListener.onStartGame()
var curStoneColor: StoneColor = StoneColor.BLACK
var curBoard = Board(BlackPlayer(), WhitePlayer(), rule)
do {
curBoard = takeTurn(curBoard, curStoneColor)
startEndEventListener.onEndTurn(curBoard.getPlayers())
curStoneColor = curStoneColor.next()
} while (curBoard.isRunning())
if (curBoard.isLose()) startEndEventListener.onEndGame(curStoneColor)
else startEndEventListener.onEndGame(curStoneColor.next())
}

private fun takeTurn(board: Board, stoneColor: StoneColor): Board {
val newStone = Stone.of(turnEventListener.onTakeTurn(stoneColor))
val newBoard = board.putStone(stoneColor, newStone)
if (newBoard == null) {
turnEventListener.onNotPlaceable()
return takeTurn(board, stoneColor)
}
return newBoard
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/domain/player/BlackPlayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain.player

import domain.rule.OmokRule
import domain.state.LoseState
import domain.state.PlayerState
import domain.state.PlayingState
import domain.stone.Stone
import domain.stone.Stones

class BlackPlayer(state: PlayerState = PlayingState()) : Player(state) {
val isLose
get() = state is LoseState

override fun putStone(stone: Stone, otherStones: Stones, rule: OmokRule): Player {
val blackStones = state.getAllStones()
if (rule.check(blackStones, otherStones, stone)) {
return BlackPlayer(LoseState(state.getAllStones()))
}
return BlackPlayer(state.add(stone, rule))
}
}
25 changes: 25 additions & 0 deletions src/main/kotlin/domain/player/Player.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package domain.player

import domain.position.Position
import domain.rule.OmokRule
import domain.state.LoseState
import domain.state.PlayerState
import domain.state.WinState
import domain.stone.Stone
import domain.stone.Stones

abstract class Player(protected val state: PlayerState) : Cloneable {
fun canPlace(): Boolean = state !is WinState && state !is LoseState

fun isPlaced(stone: Stone): Boolean = state.hasStone(stone)

fun getPositions(): List<Position> = state.getPlaced()

fun getLastStone(): Stone = state.getLastStone()

fun getAllStones(): Stones = state.getAllStones()

abstract fun putStone(stone: Stone, otherStones: Stones, rule: OmokRule): Player

public override fun clone(): Player = super.clone() as Player
}
50 changes: 50 additions & 0 deletions src/main/kotlin/domain/player/Players.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package domain.player

import domain.rule.OmokRule
import domain.rule.RenjuRule
import domain.stone.Stone
import domain.stone.StoneColor

data class Players private constructor(private val players: List<Player>, private val rule: OmokRule) {
val isRunning: Boolean
get() = players.all { it.canPlace() }
val isBlackLose: Boolean
get() = (getBlackPlayer() as BlackPlayer).isLose

constructor(blackPlayer: Player, whitePlayer: Player, rule: OmokRule) : this(
listOf(
blackPlayer.clone(),
whitePlayer.clone()
),
rule
)

fun putStone(stoneColor: StoneColor, stone: Stone): Players {
val whiteStones = getWhitePlayer().getAllStones()
val blackStones = getBlackPlayer().getAllStones()

return when (stoneColor) {
StoneColor.BLACK -> {
Players(
blackPlayer = getBlackPlayer().putStone(stone, whiteStones, RenjuRule()),
whitePlayer = getWhitePlayer(),
rule,
)
}

StoneColor.WHITE -> {
Players(
blackPlayer = getBlackPlayer(),
whitePlayer = getWhitePlayer().putStone(stone, blackStones, rule),
rule,
)
}
}
}

fun getBlackPlayer(): Player = players.first { it is BlackPlayer }

fun getWhitePlayer(): Player = players.first { it is WhitePlayer }

fun canPlace(stone: Stone): Boolean = players.none { it.isPlaced(stone) }
}
12 changes: 12 additions & 0 deletions src/main/kotlin/domain/player/WhitePlayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package domain.player

import domain.rule.OmokRule
import domain.state.PlayerState
import domain.state.PlayingState
import domain.stone.Stone
import domain.stone.Stones

class WhitePlayer(state: PlayerState = PlayingState()) : Player(state) {
override fun putStone(stone: Stone, otherStones: Stones, rule: OmokRule): Player =
WhitePlayer(state.add(stone, rule))
}
16 changes: 16 additions & 0 deletions src/main/kotlin/domain/position/Position.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package domain.position

data class Position(val row: Int, val col: Int) {
init {
require(row in POSITION_RANGE) { ROW_OUT_OF_RANGE_ERROR_MESSAGE }
require(col in POSITION_RANGE) { COLUMN_OUT_OF_RANGE_ERROR_MESSAGE }
}

companion object {
private const val MIN_BOUND = 1
private const val MAX_BOUND = 15
val POSITION_RANGE = (MIN_BOUND..MAX_BOUND)
private const val ROW_OUT_OF_RANGE_ERROR_MESSAGE = "행의 범위는 ${MIN_BOUND}부터 ${MAX_BOUND}입니다"
private const val COLUMN_OUT_OF_RANGE_ERROR_MESSAGE = "열의 범위는 ${MIN_BOUND}부터 ${MAX_BOUND}입니다"
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/domain/rule/OmokRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package domain.rule

import domain.stone.Stone
import domain.stone.Stones

interface OmokRule {
fun check(blackStones: Stones, whiteStones: Stones, startStone: Stone): Boolean
}
Loading

0 comments on commit 21d97d3

Please sign in to comment.