-
Notifications
You must be signed in to change notification settings - Fork 7
/
fp-to-the-max-1.ts
119 lines (93 loc) · 3.57 KB
/
fp-to-the-max-1.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
import { EOL } from 'os'
import { createInterface } from 'readline'
import { Async, async, attempt, defaultEnv, doFx, Fx, runFx, Sync, sync, timeout } from '../src'
// -------------------------------------------------------------------
// The number guessing game example from
// https://www.youtube.com/watch?v=sxudIMiOo68
// -------------------------------------------------------------------
// Capabilities the game will need
type Print = { print(s: string): Fx<Sync, void> }
type Read = { read: Fx<Async, string> }
type RandomInt = { randomInt(min: number, max: number): Fx<Sync, number> }
// -------------------------------------------------------------------
// Basic operations that use the capabilites
const println = (s: string) => doFx(function* ({ print }: Print) {
return yield* print(`${s}${EOL}`)
})
const ask = (prompt: string) => doFx(function* ({ print, read }: Print & Read) {
yield* print(prompt)
return yield* read
})
const randomInt = (min: number, max: number) => doFx(function* ({ randomInt }: RandomInt) {
return yield* randomInt(min, max)
})
// -------------------------------------------------------------------
// The game
// Min/max range for the number guessing game
type GameConfig = {
min: number,
max: number
}
// Core "business logic": evaluate the user's guess
const checkAnswer = (secret: number, guess: number): boolean =>
secret === guess
// Play one round of the game. Generate a number and ask the user
// to guess it.
const play = (name: string, min: number, max: number) => doFx(function* () {
const secret = yield* randomInt(min, max)
const result =
yield* attempt(timeout(3000, ask(`Dear ${name}, please guess a number from ${min} to ${max}: `)))
if (typeof result === 'string') {
const guess = Number(result)
if (!Number.isInteger(guess)) {
yield* println('You did not enter an integer!')
} else {
if (checkAnswer(secret, guess)) yield* println(`You guessed right, ${name}!`)
else yield* println(`You guessed wrong, ${name}! The number was: ${secret}`)
}
} else {
yield* println('You took too long!')
}
})
// Ask the user if they want to play again.
// Note that we keep asking until the user gives an answer we recognize
const checkContinue = (name: string) => doFx(function* () {
while (true) {
const answer = yield* ask(`Do you want to continue, ${name}? (y or n) `)
switch (answer.toLowerCase()) {
case 'y': return true
case 'n': return false
}
}
})
// Main game loop. Play round after round until the user chooses to quit
const main = doFx(function* ({ min, max }: GameConfig) {
const name = yield* ask('What is your name? ')
yield* println(`Hello, ${name} welcome to the game!`)
do {
yield* play(name, min, max)
} while (yield* checkContinue(name))
yield* println(`Thanks for playing, ${name}.`)
})
// -------------------------------------------------------------------
// Implementations of all the capabilities the game needs.
// The type system will prevent running the game until implementations
// of all capabilities have been provided.
const capabilities = {
min: 1,
max: 5,
...defaultEnv,
print: (s: string): Fx<Sync, void> =>
sync(() => void process.stdout.write(s)),
read: async<string>(k => {
const readline = createInterface({ input: process.stdin })
.once('line', s => {
readline.close()
k(s)
})
return () => readline.removeListener('line', k).close()
}),
randomInt: (min: number, max: number): Fx<Sync, number> =>
sync(() => Math.floor(min + (Math.random() * (max - min))))
}
runFx(main, capabilities)