-
Notifications
You must be signed in to change notification settings - Fork 8
/
utils.rkt
307 lines (257 loc) · 11.5 KB
/
utils.rkt
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#lang racket
(require 2htdp/image
openssl/sha1
css-tools/colors
sugar)
; A grab-bag of helper futions for identikon
(provide (all-defined-out))
; Default Constants
(define DEFAULT-MAX-USER-LENGTH 18)
(define DEFAULT-SATURATION "60%")
(define DEFAULT-LIGHTNESS "50%")
(define DEFAULT-BORDER-MAX 10)
; Data structs
(struct point (x y) #:transparent)
(struct dim (w h) #:transparent)
(struct canvas (outside inside border) #:transparent)
; Use the first and last numbers in user to generate a 6x6 grid of color values
; from min to max in 36 steps
(define (build-color-range user)
(define s (remove-duplicates user))
(define color-a (min (first s) (last s)))
(define color-b (max (first s) (last s)))
(slice-at (range color-a color-b
(/ (- color-b color-a) 36))
6))
; Turn a hue into an RGB color object
(define (make-rgb hue [sat DEFAULT-SATURATION] [lig DEFAULT-LIGHTNESS])
(define rgb (map (λ (x) (->int (* 255 x)))
(hsl->rgb (list (number->string (* 1.411 hue)) sat lig))))
(make-color (first rgb) (second rgb) (third rgb)))
; Is a number a double? ex: 33, 66
(define (double? x)
(let ([nums (string->list (number->string x))])
(cond
[(not (even? (length nums))) #f]
[(eq? (length nums) 2) (eq? (first nums) (last nums))]
[else (let-values ([(front back) (split-at nums (quotient (length nums) 2))])
(string=? (list->string front) (list->string back)))])))
; Drop images in a list next to one another
(define (row->image row)
(cond
[(empty? row) empty-image]
[else (beside (first row)
(row->image (rest row)))]))
; Convert a string into a list of string pairs
; (string-pairs "Apple") returns ("Ap" "pl" "e")
(define (string-pairs s)
(define (loop p l)
(cond
[(empty? l) (reverse p)]
[(eq? (length l) 1) (reverse (cons (list->string l) p))]
[else (loop (cons (list->string (take l 2)) p) (drop l 2))]))
(loop '() (string->list (string-join (string-split s) ""))))
; Partition list into lists of n elements
; example: (chunk-mirror 3 '(1 2 3 4 5 6)) returns
; '((1 2 3 3 2 1) (4 5 6 6 5 4))
(define (chunk-mirror xs n)
(let ([chunked (slice-at xs n)])
(map (λ (x)
(flatten (cons x (reverse x)))) chunked)))
; Partition list into lists of n elements
; example: (chunk-mirror 3 '(1 2 3 4 5 6)) returns
; '((1 2 3 1 2 3) (4 5 6 4 5 6))
(define (chunk-dupe xs n)
(let ([chunked (slice-at xs n)])
(map (λ (x)
(flatten (cons x x))) chunked)))
; Calculate the position of a position in a space within a new space
; example: where x = 155 in a 255px wide space return x in 300px space
(define (relative-position pos current-max target-max)
(* (/ pos current-max) target-max))
; Take the dimensions and calculate a border 10% of dim and the internal draw space
(define (make-canvas width height [max DEFAULT-BORDER-MAX])
(let* ([border (min (* width .1) max)]
[iw (->int (- width (* border 2)))]
[ih (->int (- height (* border 2)))]
[outside (dim width height)]
[inside (dim iw ih)])
(canvas outside inside border)))
;; ///////////////////////
;; // SHA1 Operations
;; //////////////////////
;; Convert contents of port into a list of 20 base-10 numbers from a SHA1 hash
(define (process-input-port pt)
(let* ([pairs (map (λ (x) (string->number x 16))
(string-pairs (sha1 pt)))])
(when (input-port? pt)
(close-input-port pt))
pairs))
;; Convert a string into a byte port
(define (string->numberlist str)
(process-input-port (open-input-bytes (string->bytes/utf-8 (->string str)))))
;; Convert a file into a byte portb
(define (file->numberlist filename)
(define fpath (->string filename))
(if (and (> (string-length fpath) 0) (file-exists? (string->path fpath)))
(process-input-port (open-input-file fpath #:mode 'binary))
(raise-argument-error 'file->numberlist "file-exists?" filename)))
; Pad a list with its last value to size
(define (pad-list l size)
(cond
[(empty? l) (build-list size values)]
[(< (length l) size) (pad-list (append l (list (last l))) size)]
[else l]))
; Fold over a list of lists and gather values from pos in each list into a new list
(define (gather-values pos l)
(cond
[(empty? l) '()]
[else (foldl (λ (x y) (cons (if (empty? x)
'()
(pos x)) y)) '() l)]))
; Build up a list of 12 triplets '(1 2 3) to use as color information
(define (make-triplets user [max DEFAULT-MAX-USER-LENGTH])
(let* ([initial (cond
[(empty? user) (range max)] ; fail safe for empty list
[(> (modulo (length user) max) 0) (pad-list user max)]
[else user])]
[triples (filter (λ (x) (> (length x) 0)) (slice-at (take initial max) 3))]
[firsts (slice-at (pad-list (gather-values first triples) 3) 3)]
[seconds (slice-at (pad-list (gather-values second triples) 3) 3)]
[thirds (slice-at (pad-list (gather-values third triples) 3) 3)])
(append triples firsts seconds thirds)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Tests
(module+ test
(require rackunit
sugar)
(test-case
"file->numberlist returns a list of 20 values"
(check-true (= 20 (length (file->numberlist "utils.rkt")))))
(test-case
"file->numberlist throws exn if no file exists"
(check-exn
exn:fail?
(λ () (file->numberlist "wutang.rkt")))
(check-exn
exn:fail?
(λ () (file->numberlist "utils"))))
(test-case
"file->numberlist throws exn if filename empty"
(check-exn
exn:fail?
(λ () (file->numberlist "")))))
(module+ test
(require quickcheck
sugar)
; TEST: Make canvas should calculate a border and internal area and create data structures
(define make-canvas-structs-agree
(property ([w arbitrary-natural]
[h arbitrary-natural])
(let* ([c (make-canvas w h)]
[outside (canvas-outside c)]
[inside (canvas-inside c)]
[border (min (* w .1) DEFAULT-BORDER-MAX)])
(and (canvas? c)
(dim? outside)
(dim? inside)
(= (dim-w outside) w)
(= (dim-h outside) h)
(= (canvas-border c) border)
(= (->int (- (dim-w outside) (* border 2))) (dim-w inside))
(= (->int (- (dim-h outside) (* border 2))) (dim-h inside))))))
(quickcheck make-canvas-structs-agree)
;; Ensure we get a list of 20 values
(define process-user-lengths-agree
(property ([val (choose-mixed (list
(choose-integer 1 (random 10000))
(choose-string choose-printable-ascii-char
(random 100))))])
(= 20 (length (string->numberlist val)))))
(quickcheck process-user-lengths-agree)
; string-pairs length is equal to original string without spaces
(define string-pairs-length-agree
(property ([str arbitrary-printable-ascii-string])
(= (string-length (string-trim (string-replace str " " "")))
(string-length (string-join (string-pairs str) "")))))
(quickcheck string-pairs-length-agree)
; string-pairs list contains items of length 2 or less
(define string-pairs-lengths-are-two
(property ([str arbitrary-printable-ascii-string])
(not (false? (foldl (λ (x y) (<= (string-length x) 2)) #t (string-pairs str))))))
(quickcheck string-pairs-lengths-are-two)
; chunk mirror returns lists with twice the length of the original
(define chunk-mirrors-length-doubled
(property ([lst (arbitrary-list arbitrary-natural)]
[num arbitrary-natural])
(= (* 2 (length lst)) (length (flatten (chunk-mirror lst (+ 1 num)))))))
(quickcheck chunk-mirrors-length-doubled)
; chunk mirror returns list items that are mirrors, so if we split the item list
; in half both pieces should be equal when the 2nd half is reversed
(define chunk-mirrors-items-mirrored
(property ([lst (arbitrary-list arbitrary-natural)]
[num arbitrary-natural])
(let* ([cm (chunk-mirror lst (+ 1 num))]
[results (map (λ (x)
(let-values ([(f b) (split-at x (quotient (length x) 2))])
(equal? f (reverse b))))
cm)])
(empty? (filter false? results)))))
(quickcheck chunk-mirrors-items-mirrored)
; chunk mirror returns lists with lengths equal slice-at lst num + 1
(define chunk-mirrors-length-is-round
(property ([lst (arbitrary-list arbitrary-natural)]
[num arbitrary-natural])
(let ([cm (chunk-mirror lst (+ 1 num))])
(= (length cm) (length (slice-at lst (+ 1 num)))))))
(quickcheck chunk-mirrors-length-is-round)
; chunk dupe returns list items that dupes, so if we split the item list
; in half both pieces should be equal
(define chunk-dupe-items-mirrored
(property ([lst (arbitrary-list arbitrary-natural)]
[num arbitrary-natural])
(let* ([cm (chunk-dupe lst (+ 1 num))]
[results (map (λ (x)
(let-values ([(f b) (split-at x (quotient (length x) 2))])
(equal? f b)))
cm)])
(empty? (filter false? results)))))
(quickcheck chunk-dupe-items-mirrored)
; Relative position should be reversible to original position
(define relative-position-values-agree
(property ([pos (choose-integer 0 200)]
[current (choose-integer 0 300)]
[target (choose-integer 0 500)])
(let* ([new (relative-position pos current target)])
(= (* (/ new target) current)
pos))))
(quickcheck relative-position-values-agree)
; pad-list should increase the list to size
(define pad-list-lengths-agree
(property ([lst (arbitrary-list arbitrary-natural)]
[size arbitrary-natural])
(>= (length (pad-list lst size)) size)))
(quickcheck pad-list-lengths-agree)
; gather values will builds up lists made from pos values in lst
(define gather-values-lengths-agree
(property ([lst (arbitrary-list (arbitrary-list arbitrary-natural))])
(let ([len (length lst)])
(= (length (gather-values first lst)) len))))
(quickcheck gather-values-lengths-agree)
; make-triplets should always return a list of 12 items
(define make-triplets-lengths-agree
(property ([lst (arbitrary-list arbitrary-natural)])
(= (length (make-triplets lst)) 12)))
(quickcheck make-triplets-lengths-agree)
; make-triplets should always return a list of 12 lists of 3 items
(define make-triplets-items-agree
(property ([lst (arbitrary-list arbitrary-natural)])
(let ([t (make-triplets lst)])
(empty? (filter false? (map (λ (x) (and (list? x)
(= (length x) 3))) t))))))
(quickcheck make-triplets-items-agree)
; Test build-color-range
(define make-color-ranges-lengths-agree
(property ([user (choose-list (choose-integer 0 255) 20)])
(= 6 (length (build-color-range user)))))
(quickcheck make-color-ranges-lengths-agree))