-
-
Notifications
You must be signed in to change notification settings - Fork 269
/
Copy pathstore.ts
132 lines (121 loc) · 4.63 KB
/
store.ts
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
import * as KeyValueStore from "@effect/platform/KeyValueStore"
import * as Schema from "@effect/schema/Schema"
import * as Arr from "effect/Array"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import { pipe } from "effect/Function"
import * as Layer from "effect/Layer"
import * as Option from "effect/Option"
import { CoordinatesOccupiedError, Mine, Ship, ShipExistsError, ShipNotFoundError } from "./domain.js"
/**
* Represents the storage layer for the Naval Fate command-line application.
*/
export interface NavalFateStore {
createShip(name: string): Effect.Effect<Ship, ShipExistsError>
moveShip(
name: string,
x: number,
y: number
): Effect.Effect<Ship, CoordinatesOccupiedError | ShipNotFoundError>
shoot(x: number, y: number): Effect.Effect<void>
setMine(x: number, y: number): Effect.Effect<void>
removeMine(x: number, y: number): Effect.Effect<void>
}
export const NavalFateStore = Context.GenericTag<NavalFateStore>("NavalFateStore")
export const make = Effect.gen(function*($) {
const shipsStore = yield* $(Effect.map(
KeyValueStore.KeyValueStore,
(store) => store.forSchema(Schema.ReadonlyMap({ key: Schema.String, value: Ship }))
))
const minesStore = yield* $(Effect.map(
KeyValueStore.KeyValueStore,
(store) => store.forSchema(Schema.Array(Mine))
))
const getShips = shipsStore.get("ships").pipe(
Effect.map(Option.getOrElse<ReadonlyMap<string, Ship>>(() => new Map())),
Effect.orDie
)
const getMines = minesStore.get("mines").pipe(
Effect.map(Option.getOrElse<ReadonlyArray<Mine>>(() => [])),
Effect.orDie
)
const setShips = (ships: ReadonlyMap<string, Ship>) => shipsStore.set("ships", ships).pipe(Effect.orDie)
const setMines = (mines: ReadonlyArray<Mine>) => minesStore.set("mines", mines).pipe(Effect.orDie)
const createShip: NavalFateStore["createShip"] = (name) =>
Effect.gen(function*($) {
const oldShips = yield* $(getShips)
const foundShip = Option.fromNullable(oldShips.get(name))
if (Option.isSome(foundShip)) {
return yield* $(Effect.fail(new ShipExistsError({ name })))
}
const ship = Ship.create(name)
const newShips = new Map(oldShips).set(name, ship)
yield* $(setShips(newShips))
return ship
})
const moveShip: NavalFateStore["moveShip"] = (name, x, y) =>
Effect.gen(function*($) {
const oldShips = yield* $(getShips)
const foundShip = Option.fromNullable(oldShips.get(name))
if (Option.isNone(foundShip)) {
return yield* $(Effect.fail(new ShipNotFoundError({ name, x, y })))
}
const shipAtCoords = pipe(
Arr.fromIterable(oldShips.values()),
Arr.findFirst((ship) => ship.hasCoordinates(x, y))
)
if (Option.isSome(shipAtCoords)) {
return yield* $(Effect.fail(
new CoordinatesOccupiedError({ name: shipAtCoords.value.name, x, y })
))
}
const mines = yield* $(getMines)
const mineAtCoords = Arr.findFirst(mines, (mine) => mine.hasCoordinates(x, y))
const ship = Option.isSome(mineAtCoords)
? foundShip.value.move(x, y).destroy()
: foundShip.value.move(x, y)
const newShips = new Map(oldShips).set(name, ship)
yield* $(setShips(newShips))
return ship
})
const shoot: NavalFateStore["shoot"] = (x, y) =>
Effect.gen(function*($) {
const oldShips = yield* $(getShips)
const shipAtCoords = pipe(
Arr.fromIterable(oldShips.values()),
Arr.findFirst((ship) => ship.hasCoordinates(x, y))
)
if (Option.isSome(shipAtCoords)) {
const ship = shipAtCoords.value.destroy()
const newShips = new Map(oldShips).set(ship.name, ship)
yield* $(setShips(newShips))
}
})
const setMine: NavalFateStore["setMine"] = (x, y) =>
Effect.gen(function*($) {
const mines = yield* $(getMines)
const mineAtCoords = Arr.findFirst(mines, (mine) => mine.hasCoordinates(x, y))
if (Option.isNone(mineAtCoords)) {
const mine = Mine.create(x, y)
const newMines = Arr.append(mines, mine)
yield* $(setMines(newMines))
}
})
const removeMine: NavalFateStore["removeMine"] = (x, y) =>
Effect.gen(function*($) {
const mines = yield* $(getMines)
const mineAtCoords = Arr.findFirstIndex(mines, (mine) => mine.hasCoordinates(x, y))
if (Option.isSome(mineAtCoords)) {
const newMines = Arr.remove(mines, mineAtCoords.value)
yield* $(setMines(newMines))
}
})
return NavalFateStore.of({
createShip,
moveShip,
shoot,
setMine,
removeMine
})
})
export const layer = Layer.effect(NavalFateStore, make)