Complete Projects
Add an SUnit test for the *blinker* oscillator (three cells in a row flip between horizontal and vertical).
42.1 Word-frequency command-line tool (headless Pharo)
"wordfreq.st — run: ./pharo Pharo.image st --quit wordfreq.st"
| text words counts out |
text := 'input.txt' asFileReference contents asLowercase.
words := (text substrings: ' .,;:!?()"''-' , String crlf , String tab)
reject: [ :w | w size < 3 ].
counts := words asBag.
out := Stdio stdout.
(counts sortedCounts first: (10 min: counts asSet size)) do: [ :assoc |
out
nextPutAll: (assoc value padRightTo: 15);
nextPutAll: assoc key printString;
lf ].
out flush.42.2 Conway's Game of Life (model + Morphic view)
Add an SUnit test for the *blinker* oscillator (three cells in a row flip between horizontal and vertical).
Object << #LifeGrid
slots: { #cells . #dimension };
package: 'Life'
LifeGrid class >> dimension: n
^ self new setDimension: n
LifeGrid >> setDimension: n
dimension := n.
cells := Array new: n * n withAll: false
LifeGrid >> dimension
^ dimension
LifeGrid >> indexOf: x at: y
^ ((y - 1) \\ dimension) * dimension + ((x - 1) \\ dimension) + 1 "wraps around"
LifeGrid >> at: x at: y
^ cells at: (self indexOf: x at: y)
LifeGrid >> at: x at: y put: aBoolean
cells at: (self indexOf: x at: y) put: aBoolean
LifeGrid >> neighboursOf: x at: y
| n |
n := 0.
-1 to: 1 do: [ :dx | -1 to: 1 do: [ :dy |
((dx = 0 and: [ dy = 0 ]) not and: [ self at: x + dx at: y + dy ])
ifTrue: [ n := n + 1 ] ] ].
^ n
LifeGrid >> step
| next |
next := Array new: cells size.
1 to: dimension do: [ :x | 1 to: dimension do: [ :y |
| n |
n := self neighboursOf: x at: y.
next at: (self indexOf: x at: y) put: (n = 3 or: [ n = 2 and: [ self at: x at: y ] ]) ] ].
cells := next
LifeGrid >> randomize
1 to: cells size do: [ :i | cells at: i put: 4 atRandom = 1 ]
Morph << #LifeMorph
slots: { #grid };
…(LifeMorph new grid: ((LifeGrid dimension: 60) randomize; yourself)) openInWorld.42.3 URL shortener (Teapot + in-memory store)
Extensions: persist with Voyage, count visits with Announcements, add a Spec2 admin UI, test with ZnClient.
Object << #Shortener
slots: { #urls . #server };
package: 'Shortener'
Shortener >> initialize
super initialize.
urls := Dictionary new
Shortener >> shorten: aUrl
| code |
[ code := String new: 6 streamContents: [ :s |
6 timesRepeat: [ s nextPut: 'abcdefghijkmnpqrstuvwxyz23456789' atRandom ] ].
urls includesKey: code ] whileTrue.
urls at: code put: aUrl.
^ code
Shortener >> start
server := (Teapot configure: { #port -> 8080 })
POST: '/shorten' -> [ :req |
{ #code -> (self shorten: (req at: #url)) } asDictionary ]; output: #json;
GET: '/<code>' -> [ :req |
TeaResponse redirect location: (urls at: (req at: #code)) ];
exception: KeyNotFound -> (TeaResponse notFound body: 'Unknown code');
start
Shortener >> stop
server stopcurl -X POST -d "url=https://pharo.org" http://localhost:8080/shorten # {"code":"k3x9ab"}
curl -i http://localhost:8080/k3x9ab # 303 → https://pharo.org42.4 Other complete project ideas
Bank system (Chapters 13–33 combined): accounts, transactions, custom exceptions, announcements, SUnit tests, a Spec2 UI, and a Teapot JSON API — all in one Git repository with a baseline.
Todo application: model with announcements, Spec2 master–detail UI, STON file persistence, Iceberg + smalltalkCI.
Code-metrics dashboard: walk packages with reflection (classes, methods, lines of code, senders), display with Roassal, export to CSV.