This repository was archived by the owner on Apr 5, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathGame.fs
More file actions
164 lines (131 loc) · 5.17 KB
/
Copy pathGame.fs
File metadata and controls
164 lines (131 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
module FsUno.Domain.Game
open Deck
// Commands
type Command =
| StartGame of StartGame
| PlayCard of PlayCard
and StartGame = {
GameId: GameId
PlayerCount: int
FirstCard: Card }
and PlayCard = {
GameId: GameId
Player: int
Card: Card }
// Events
type Event =
| GameStarted of GameStartedEvent
| CardPlayed of CardPlayedEvent
| PlayerPlayedAtWrongTurn of PlayerPlayedAtWrongTurnEvent
| PlayerPlayedWrongCard of PlayerPlayedWrongCardEvent
| DirectionChanged of DirectionChangedEvent
// Type representing current player turn; All operations should be encapsulated
type Turn = {
Player: int
PlayerCount:int
Direction:Direction }
with
static member empty = { Player= 0; PlayerCount = 1; Direction = ClockWise }
static member start player count = {Player = player; PlayerCount = count; Direction = ClockWise }
member turn.next =
match turn.Direction with
| ClockWise -> { turn with Player = (turn.Player + 1) % turn.PlayerCount }
| CounterClockWise -> { turn with Player = (turn.Player + turn.PlayerCount - 1) % turn.PlayerCount } // the + count is here to avoid having negative result
member turn.skip = turn.next.next
member turn.reverse =
match turn.Direction with
| ClockWise -> { turn with Direction = CounterClockWise }
| CounterClockWise -> { turn with Direction = ClockWise }
member turn.setPlayer player =
if player < 0 || player >= turn.PlayerCount then
invalidArg "player" "The player value should be between 0 and player count"
{ turn with Player = player }
member turn.setDirection direction =
{ turn with Turn.Direction = direction }
// State
type State = {
GameAlreadyStarted: bool
Turn: Turn
TopCard: Card }
with
static member initial = {
GameAlreadyStarted = false
Turn = Turn.empty
TopCard = Digit(digit 0,Red) }
// Operations on the Game aggregate
let color = function
| Digit(_,c) -> c
| KickBack c -> c
| Skip c -> c
let sameColor c1 c2 = color c1 = color c2
let (|SameColor|_|) (c1,c2) = if sameColor c1 c2 then Some (color c1) else None
let (|SameValue|_|) = function
| Digit(n1,_), Digit(n2,_) when n1 = n2 -> Some()
| KickBack _, KickBack _ -> Some()
| _ -> None
let startGame (command: StartGame) state =
if command.PlayerCount <= 2 then invalidArg "playerCount" "There should be at least 3 players"
if state.GameAlreadyStarted then invalidOp "The game cannot be started more than once"
let gameStarted firstPlayer =
GameStarted { GameId = command.GameId
PlayerCount = command.PlayerCount
FirstCard = command.FirstCard
FirstPlayer = firstPlayer }
match command.FirstCard with
| KickBack _ ->
[ gameStarted 0
DirectionChanged { GameId = command.GameId; Direction = CounterClockWise } ]
| Skip _ -> [ gameStarted 1 ]
| _ -> [ gameStarted 0]
let playCard (command: PlayCard) state =
if state.Turn.Player <> command.Player then
[ PlayerPlayedAtWrongTurn { GameId = command.GameId
Player = command.Player
Card = command.Card } ]
else
match command.Card, state.TopCard with
| SameColor _
| SameValue ->
let cardPlayed nextPlayer =
CardPlayed { GameId = command.GameId
Player = command.Player
Card = command.Card
NextPlayer = nextPlayer}
match command.Card with
| KickBack _ ->
let nextTurn = state.Turn.reverse.next
[ cardPlayed nextTurn.Player
DirectionChanged { GameId = command.GameId
Direction = nextTurn.Direction } ]
| Skip _ ->
let nextTurn = state.Turn.skip
[ cardPlayed nextTurn.Player ]
| _ ->
let nextTurn = state.Turn.next
[ cardPlayed nextTurn.Player ]
| _ -> [ PlayerPlayedWrongCard { GameId = command.GameId; Player = command.Player; Card = command.Card} ]
// Map commands to aggregate operations
let handle = function
| StartGame command -> startGame command
| PlayCard command -> playCard command
// Identify Aggregate instance to direct Commands to
let gameId = function
| StartGame c -> c.GameId
| PlayCard c -> c.GameId
// Applies State changes for events
type State with
static member apply state = function
| GameStarted event ->
{ GameAlreadyStarted = true
Turn = Turn.start event.FirstPlayer event.PlayerCount
TopCard = event.FirstCard }
| CardPlayed event ->
{ state with
Turn = state.Turn.setPlayer event.NextPlayer
TopCard = event.Card }
| DirectionChanged event ->
{ state with
Turn = state.Turn.setDirection event.Direction }
| PlayerPlayedAtWrongTurn _
| PlayerPlayedWrongCard _ ->
state