2019-12-05 18:17:05 +01:00
|
|
|
module Board
|
|
|
|
|
2019-12-09 13:20:39 +01:00
|
|
|
open FSharpPlus.Lens
|
|
|
|
|
2019-12-06 10:51:27 +01:00
|
|
|
module Side =
|
2019-12-09 10:44:05 +01:00
|
|
|
open Card.Card
|
2019-12-05 18:17:05 +01:00
|
|
|
|
2019-12-09 10:44:05 +01:00
|
|
|
type Side<'s> =
|
|
|
|
{ field: CardInstance<'s> option
|
2019-12-12 20:59:51 +01:00
|
|
|
monsters: CardInstance<'s> option list
|
|
|
|
spells: CardInstance<'s> option list
|
2019-12-09 10:44:05 +01:00
|
|
|
graveyard: CardInstance<'s> list
|
|
|
|
deck: CardInstance<'s> list }
|
2019-12-05 18:25:03 +01:00
|
|
|
|
2019-12-09 13:20:39 +01:00
|
|
|
module Side =
|
|
|
|
let inline field f side = f side.field <&> fun v -> { side with field = v }
|
|
|
|
let inline monsters f side = f side.monsters <&> fun v -> { side with monsters = v }
|
|
|
|
let inline spells f side = f side.spells <&> fun v -> { side with spells = v }
|
|
|
|
let inline graveyard f side = f side.graveyard <&> fun v -> { side with graveyard = v }
|
|
|
|
let inline deck f side = f side.deck <&> fun v -> { side with deck = v }
|
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
let emptyRow _ = List.init 5 <| fun _ -> None
|
|
|
|
|
|
|
|
let emptySide _ =
|
2019-12-06 10:51:27 +01:00
|
|
|
{ field = None
|
2019-12-12 20:59:51 +01:00
|
|
|
monsters = emptyRow()
|
|
|
|
spells = emptyRow()
|
2019-12-06 10:51:27 +01:00
|
|
|
graveyard = []
|
|
|
|
deck = [] }
|
|
|
|
|
|
|
|
|
|
|
|
module Player =
|
|
|
|
open Side
|
2019-12-09 10:44:05 +01:00
|
|
|
open Card.Card
|
2019-12-06 10:51:27 +01:00
|
|
|
|
2019-12-07 18:56:20 +01:00
|
|
|
type PlayerState =
|
|
|
|
| InGame
|
2019-12-12 15:34:21 +01:00
|
|
|
| Won of reason: string
|
2019-12-07 18:56:20 +01:00
|
|
|
| Lost of reason: string
|
|
|
|
|
2019-12-09 10:44:05 +01:00
|
|
|
type Player<'s> =
|
2019-12-06 10:51:27 +01:00
|
|
|
{ lifePoints: int
|
2019-12-09 10:44:05 +01:00
|
|
|
side: Side<'s>
|
|
|
|
hand: CardInstance<'s> list
|
2019-12-10 10:06:28 +01:00
|
|
|
state: PlayerState
|
2019-12-12 20:59:51 +01:00
|
|
|
id: int
|
|
|
|
lastNormalSummon: int }
|
2019-12-06 10:51:27 +01:00
|
|
|
|
2019-12-09 13:20:39 +01:00
|
|
|
module Player =
|
|
|
|
let inline lifePoints f player = f player.lifePoints <&> fun v -> { player with lifePoints = v }
|
|
|
|
let inline side f player = f player.side <&> fun v -> { player with side = v }
|
|
|
|
let inline hand f player = f player.hand <&> fun v -> { player with hand = v }
|
|
|
|
let inline state f player = f player.state <&> fun v -> { player with state = v }
|
2019-12-10 10:06:28 +01:00
|
|
|
let inline _id f player = f player.id <&> fun v -> { player with id = v }
|
2019-12-12 20:59:51 +01:00
|
|
|
let inline lastNormalSummon f player =
|
|
|
|
f player.lastNormalSummon <&> fun v -> { player with lastNormalSummon = v }
|
2019-12-10 10:06:28 +01:00
|
|
|
|
2019-12-09 13:20:39 +01:00
|
|
|
let inline deck f player = (side << Side.deck) f player
|
2019-12-15 00:30:17 +01:00
|
|
|
let inline monsters f player = (side << Side.monsters) f player
|
2019-12-09 13:20:39 +01:00
|
|
|
|
2019-12-10 10:06:28 +01:00
|
|
|
let initialPlayer lp id =
|
2019-12-06 10:51:27 +01:00
|
|
|
{ lifePoints = lp
|
2019-12-12 20:59:51 +01:00
|
|
|
side = emptySide()
|
2019-12-07 18:56:20 +01:00
|
|
|
hand = []
|
2019-12-10 10:06:28 +01:00
|
|
|
state = InGame
|
2019-12-12 20:59:51 +01:00
|
|
|
id = id
|
|
|
|
lastNormalSummon = -1 }
|
2019-12-09 10:44:05 +01:00
|
|
|
|
2019-12-07 18:31:53 +01:00
|
|
|
module Turn =
|
|
|
|
type Phase =
|
|
|
|
| Draw
|
|
|
|
| Standby
|
|
|
|
| Main1
|
|
|
|
| Battle
|
|
|
|
| Main2
|
|
|
|
| End
|
2019-12-06 10:51:27 +01:00
|
|
|
|
2019-12-09 13:20:39 +01:00
|
|
|
let nextPhase (turn, phase) =
|
|
|
|
match phase with
|
|
|
|
| Draw -> (turn, Standby)
|
|
|
|
| Standby -> (turn, Main1)
|
|
|
|
| Main1 -> (turn, Battle)
|
|
|
|
| Battle -> (turn, Main2)
|
|
|
|
| Main2 -> (turn, End)
|
|
|
|
| End -> (turn + 1, Draw)
|
2019-12-06 10:51:27 +01:00
|
|
|
|
|
|
|
module Board =
|
2019-12-07 18:31:53 +01:00
|
|
|
open Turn
|
2019-12-09 10:44:05 +01:00
|
|
|
open Card
|
2019-12-10 10:06:28 +01:00
|
|
|
open Player
|
2019-12-06 10:51:27 +01:00
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
|
2019-12-09 10:44:05 +01:00
|
|
|
type Player = Player.Player<Board>
|
|
|
|
|
|
|
|
and Board =
|
2019-12-07 18:31:53 +01:00
|
|
|
{ players: Player * Player
|
2019-12-09 13:20:39 +01:00
|
|
|
moment: int * Phase }
|
|
|
|
|
|
|
|
module Board =
|
|
|
|
let inline players f board = f board.players <&> fun v -> { board with players = v }
|
|
|
|
let inline moment f board = f board.moment <&> fun v -> { board with moment = v }
|
|
|
|
|
|
|
|
let inline turn f board = (moment << _1) f board
|
|
|
|
let inline phase f board = (moment << _2) f board
|
|
|
|
|
|
|
|
let inline currentPlayer f board =
|
|
|
|
if (view turn board) % 2 = 0 then (players << _2) f board
|
|
|
|
else (players << _1) f board
|
2019-12-07 18:31:53 +01:00
|
|
|
|
2019-12-10 10:06:28 +01:00
|
|
|
let inline currentPlayerId f board = (currentPlayer << Player._id) f board
|
|
|
|
let inline currentPlayerState f board = (currentPlayer << Player.state) f board
|
|
|
|
let inline currentPlayerDeck f board = (currentPlayer << Player.deck) f board
|
|
|
|
let inline currentPlayerHand f board = (currentPlayer << Player.hand) f board
|
2019-12-12 20:59:51 +01:00
|
|
|
let inline currentPlayerLastNormalSummon f board = (currentPlayer << Player.lastNormalSummon) f board
|
2019-12-15 00:30:17 +01:00
|
|
|
let inline currentPlayerMonsters f board = (currentPlayer << Player.monsters) f board
|
2019-12-10 10:06:28 +01:00
|
|
|
|
2019-12-12 15:34:21 +01:00
|
|
|
let inline firstPlayer f board = (players << _1) f board
|
|
|
|
let inline secondPlayer f board = (players << _2) f board
|
|
|
|
|
2019-12-09 10:44:05 +01:00
|
|
|
type Card = Card.Card<Board>
|
|
|
|
|
|
|
|
type CardInstance = Card.CardInstance<Board>
|
|
|
|
|
2019-12-15 00:30:17 +01:00
|
|
|
type Monster = Card.Monster<Board>
|
|
|
|
|
2019-12-09 10:44:05 +01:00
|
|
|
type Effect = Effect.Effect<Board>
|
|
|
|
|
|
|
|
type Condition = Effect.Condition<Board>
|
|
|
|
|
|
|
|
type Action = Effect.Action<Board>
|
|
|
|
|
2019-12-07 18:31:53 +01:00
|
|
|
let emptyBoard =
|
2019-12-12 15:34:21 +01:00
|
|
|
{ players = (initialPlayer 8000 0, initialPlayer 8000 1)
|
2019-12-09 13:20:39 +01:00
|
|
|
moment = 0, Draw }
|
2019-12-07 18:31:53 +01:00
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
module Client =
|
2019-12-07 18:31:53 +01:00
|
|
|
open Player
|
2019-12-12 20:59:51 +01:00
|
|
|
open Turn
|
2019-12-09 10:44:05 +01:00
|
|
|
|
2019-12-10 15:09:06 +01:00
|
|
|
type Log =
|
|
|
|
| CardToHand of string
|
|
|
|
| NewPhase of Phase
|
2019-12-12 15:34:21 +01:00
|
|
|
| StateChanged of PlayerState * PlayerState
|
2019-12-12 20:59:51 +01:00
|
|
|
| ChooseZone of int list
|
2019-12-07 18:31:53 +01:00
|
|
|
|
2019-12-10 15:09:06 +01:00
|
|
|
type Client = Log -> int
|
2019-12-10 10:06:28 +01:00
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
let rec chooseZone client free =
|
|
|
|
let freeIndices = List.mapi (fun i _ -> i) free
|
|
|
|
let command = ChooseZone freeIndices
|
|
|
|
let result = client command
|
|
|
|
|
|
|
|
if List.contains result freeIndices then free.[result]
|
|
|
|
else chooseZone client free
|
|
|
|
|
|
|
|
module Zone =
|
|
|
|
open Player
|
|
|
|
open Side
|
2019-12-15 00:30:17 +01:00
|
|
|
open Board
|
2019-12-12 20:59:51 +01:00
|
|
|
|
2019-12-15 00:30:17 +01:00
|
|
|
let freeMonsterZones (player: Player) = List.filter Option.isNone player.side.monsters
|
2019-12-13 11:00:22 +01:00
|
|
|
let freeMonsterZoneCount = freeMonsterZones >> List.length
|
|
|
|
let hasFreeMonsterZones = (>=) << freeMonsterZoneCount
|
2019-12-12 20:59:51 +01:00
|
|
|
let hasFreeMonsterZone player = hasFreeMonsterZones player 1
|
|
|
|
|
2019-12-15 00:30:17 +01:00
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
module Summon =
|
2019-12-13 11:00:22 +01:00
|
|
|
open Card.Card
|
2019-12-15 00:30:17 +01:00
|
|
|
open Card
|
2019-12-12 20:59:51 +01:00
|
|
|
open Board
|
|
|
|
open Zone
|
|
|
|
open Client
|
|
|
|
|
|
|
|
module Normal =
|
2019-12-15 00:30:17 +01:00
|
|
|
let inline numberOfTributes (monster: Monster) =
|
2019-12-13 11:00:22 +01:00
|
|
|
let level = monster ^. Card.level
|
|
|
|
|
|
|
|
if level <= 4 then 0
|
|
|
|
elif level <= 6 then 1
|
|
|
|
else 2
|
|
|
|
|
2019-12-15 01:26:52 +01:00
|
|
|
let isNormalSummonable board monster =
|
|
|
|
let requiredTributes = numberOfTributes monster
|
2019-12-15 00:30:17 +01:00
|
|
|
|
2019-12-15 01:26:52 +01:00
|
|
|
let possibleTributes =
|
|
|
|
board ^. Board.currentPlayerMonsters
|
|
|
|
|> List.filter Option.isSome
|
|
|
|
|> List.length
|
2019-12-15 00:30:17 +01:00
|
|
|
|
2019-12-15 01:26:52 +01:00
|
|
|
let freeZones = 5 - possibleTributes + requiredTributes
|
2019-12-15 00:30:17 +01:00
|
|
|
|
2019-12-15 01:26:52 +01:00
|
|
|
requiredTributes <= possibleTributes && freeZones > 0
|
2019-12-15 00:30:17 +01:00
|
|
|
|
|
|
|
|
|
|
|
let hasNormalSummonableMonster board =
|
|
|
|
let hand = board ^. Board.currentPlayerHand
|
2019-12-15 01:26:52 +01:00
|
|
|
let monsters = List.choose monster hand
|
2019-12-15 00:30:17 +01:00
|
|
|
|
2019-12-15 01:26:52 +01:00
|
|
|
List.exists <| isNormalSummonable board <| monsters
|
2019-12-15 00:30:17 +01:00
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
let canNormalSummon board =
|
2019-12-15 00:30:17 +01:00
|
|
|
hasNormalSummonableMonster board && board ^. Board.currentPlayerLastNormalSummon < board ^. Board.turn
|
2019-12-12 20:59:51 +01:00
|
|
|
|
|
|
|
let performNormalSummon client board =
|
|
|
|
let free = freeMonsterZones <| board ^. Board.currentPlayer
|
|
|
|
let zone = chooseZone client free
|
|
|
|
|
|
|
|
let turn = board ^. Board.turn
|
|
|
|
|
|
|
|
board |> Board.currentPlayerLastNormalSummon .-> turn
|
|
|
|
|
|
|
|
module Game =
|
|
|
|
open Turn
|
|
|
|
open Player
|
|
|
|
open Board
|
|
|
|
open Summon.Normal
|
|
|
|
open Client
|
|
|
|
|
2019-12-10 10:06:28 +01:00
|
|
|
let isCurrentPlayer (board: Board) (player: Player) = (board ^. Board.currentPlayerId) = player.id
|
|
|
|
|
|
|
|
let canDrawCard (board: Board) (player: Player) =
|
2019-12-10 15:09:06 +01:00
|
|
|
isCurrentPlayer board player && board ^. Board.phase = Draw && board ^. Board.turn <> 0
|
2019-12-10 10:06:28 +01:00
|
|
|
|
|
|
|
let draw (board: Board) =
|
|
|
|
match board ^. Board.currentPlayerDeck with
|
2019-12-12 15:34:21 +01:00
|
|
|
| [] -> board |> Board.currentPlayerState .-> Lost "deckout"
|
2019-12-07 18:31:53 +01:00
|
|
|
| card :: deck ->
|
2019-12-10 10:06:28 +01:00
|
|
|
let hand = card :: (board ^. Board.currentPlayerHand)
|
2019-12-07 18:31:53 +01:00
|
|
|
|
2019-12-10 10:06:28 +01:00
|
|
|
board
|
|
|
|
|> Board.currentPlayerHand .-> hand
|
|
|
|
|> Board.currentPlayerDeck .-> deck
|
2019-12-07 18:31:53 +01:00
|
|
|
|
2019-12-09 13:20:39 +01:00
|
|
|
let toDeckBottom (card: CardInstance) (player: Player) = over Player.deck (fun d -> card :: d) player
|
2019-12-07 18:31:53 +01:00
|
|
|
|
2019-12-12 20:59:51 +01:00
|
|
|
let handleMainPhase client board =
|
|
|
|
if canNormalSummon board then performNormalSummon client board
|
|
|
|
else board
|
|
|
|
|
2019-12-10 15:09:06 +01:00
|
|
|
let processPhase client board =
|
|
|
|
match board ^. Board.phase with
|
2019-12-12 15:34:21 +01:00
|
|
|
| Draw ->
|
|
|
|
if canDrawCard board <| board ^. Board.currentPlayer then draw board
|
|
|
|
else board
|
2019-12-12 20:59:51 +01:00
|
|
|
| Main1 -> handleMainPhase client board
|
|
|
|
| Main2 -> handleMainPhase client board
|
2019-12-10 15:09:06 +01:00
|
|
|
| _ -> board
|
|
|
|
|
|
|
|
let switchPhases (client: Client) board =
|
|
|
|
let newBoard = over Board.moment nextPhase board
|
|
|
|
|
|
|
|
NewPhase <| newBoard ^. Board.phase
|
|
|
|
|> client
|
|
|
|
|> ignore
|
|
|
|
|
|
|
|
newBoard
|
|
|
|
|
2019-12-12 15:34:21 +01:00
|
|
|
|
|
|
|
let getPlayerStates board =
|
|
|
|
(board ^. (Board.firstPlayer << Player.state), board ^. (Board.secondPlayer << Player.state))
|
|
|
|
|
|
|
|
|
|
|
|
let resolvePlayerStates (p1, p2) =
|
|
|
|
let s1, s2 = p1.state, p2.state
|
|
|
|
|
|
|
|
match s1 with
|
|
|
|
| Lost reason ->
|
|
|
|
match s2 with
|
|
|
|
| InGame -> p1, p2 |> Player.state .-> Won reason
|
|
|
|
| _ -> p1, p2
|
|
|
|
| Won reason ->
|
|
|
|
match s2 with
|
|
|
|
| InGame -> p1, p2 |> Player.state .-> Lost reason
|
|
|
|
| _ -> p1, p2
|
|
|
|
| InGame ->
|
|
|
|
match s2 with
|
|
|
|
| InGame -> p1, p2
|
|
|
|
| Won reason -> p1 |> Player.state .-> Lost reason, p2
|
|
|
|
| Lost reason -> p1 |> Player.state .-> Won reason, p2
|
|
|
|
|
|
|
|
let resolveBoardState board = over Board.players resolvePlayerStates board
|
|
|
|
|
2019-12-10 15:09:06 +01:00
|
|
|
let rec game board (client: Client) =
|
|
|
|
let newBoard =
|
|
|
|
(processPhase client)
|
2019-12-12 15:34:21 +01:00
|
|
|
>> resolveBoardState
|
2019-12-10 15:09:06 +01:00
|
|
|
<| board
|
2019-12-12 15:34:21 +01:00
|
|
|
|
2019-12-10 15:09:06 +01:00
|
|
|
let currentState = newBoard ^. Board.currentPlayerState
|
|
|
|
|
|
|
|
if currentState <> InGame then
|
2019-12-12 15:34:21 +01:00
|
|
|
let newStates = getPlayerStates newBoard
|
|
|
|
client <| StateChanged newStates |> ignore
|
2019-12-10 15:09:06 +01:00
|
|
|
else
|
2019-12-12 15:34:21 +01:00
|
|
|
game <| switchPhases client newBoard <| client
|