Starter Project Idea
… are we really implementing Wordle one more time? Of course not, that would be ridiculous. We are going to do it two more times! There are two-2-three reasons why.
Zeroth, I (still) like Wordle, and this is part of my approach to work-life balance.
First, you’ve worked on Wordle clones a couple times already, but that was all in the distant past. Now you know a lot more about functional programming and Haskell. So, this is an opportunity to revisit your work with an eye towards polishing and refactoring.
Second, although command-line user interfaces should not be viewed askance, it would be fun to clone the original web-based GUI. Pure functional programming is a great tool for any programming job, and is particularly so for reactive systems like web applications. (As a form of empirical evidence, see the popularity of React/Redux.) This assignment will hardly scratch the surface of web development, but it will get you set up with a couple libraries and perhaps whet the appetite for more serious Haskell web development in the future.
Well, I don’t know about you, but I’m convinced!
Here is a basic (HTML/CSS) setup for a Wordle interface. This GUI is inert: keyboard and mouse presses have no effect, and there is no (JavaScript) logic implementing the gameplay. In this assignment, you will give life to this interface (using Haskell) in a couple ways.
If you are not familiar with HTML or web programming, don’t fret. You don’t need to know much about HTML in order to complete this assignment. An HTML page is essentially a tree of nodes (also called elements), where each node has:
A tag that describes the purpose of its content:
<h1>
for headings, <p>
for paragraphs, <img>
for images, <ol>
for ordered lists, <li>
for list items, etc.
Zero or more global
attributes such as a unique identifier ("id"
)
for the node, and zero or more tag-specific
attributes such as the URL for an image
("src"
) or the target of a hyperlink
("href"
).
Zero or more child nodes. For example, an
<ol>
will typically have one or more
<li>
nodes. Some kinds of nodes, such as
<p>
aragraphs, typically have one text
node which is a leaf of the tree — it doesn’t have any
attributes or children, but rather carries actual text content to be
displayed.
Open the HTML file that draws the GUI above — click something like View > Developer > Page Source in Chrome, or Tools > Browser Tools > Page Source in Firefox — and you will see something like:
<html>
<head>
<title>Oh My Wordle!</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8" />
</head>
<body>
<h1>Oh My Wordle!</h1>
<p id="message-panel">A place to display messages...</p>
<div id="tile-board">
<div class="row">
<span class="tile"></span>
<span class="tile"></span>
<span class="tile"></span>
<span class="tile"></span>
<span class="tile"></span>
</div>
...</div>
...<div id="visual-keyboard">
<div>
<button id="key-q" onclick="keyOrButtonPressed('Q');">Q</button>
<button id="key-w" onclick="keyOrButtonPressed('W');">W</button>
<button id="key-e" onclick="keyOrButtonPressed('E');">E</button>
<button id="key-r" onclick="keyOrButtonPressed('R');">R</button>
<button id="key-t" onclick="keyOrButtonPressed('T');">T</button>
<button id="key-y" onclick="keyOrButtonPressed('Y');">Y</button>
...</div>
</body>
</html>
The <body>
is where all the content goes.
Notice the <h1>
, <p>
aragraph,
<button>
s: these all have text nodes as children,
with text to render in the final document.
There are a couple ways to logically group one or more elements: a <div>
typically creates a new vertical block, whereas a <span>
creates a group within a particular line.
Several nodes have "id"
attributes that assign unique
identifiers, and several nodes have "class"
attributes that
define what set of CSS classes a node belongs to. Identifiers and CSS
classes are useful for styling elements (discussed below).
Notice also the "onlick"
attribute of buttons, which
specifies what JavaScript code to run in response to button clicks. (The
JavaScript functions here, which have suggestive names, are undefined.
In our implementations, we will specify event handlers in a couple
library-specific ways.)
The HTML markup above defines the content of the document, which is rendered by default according to browser and user settings.
If you open up the CSS file, you
will see definitions for how to style the different pieces of the
interface. This stylesheet is why the interface above looks the way that
it does. (Try commenting out the
<link rel="stylesheet" ... />
line in the
index.html
file and see what the defaults look like.)
H A S K
You don’t need to know anything about CSS stylesheets for this assignment; the given CSS file will be included verbatim in your projects. All you need to notice are the following four CSS classes, which can be used to style letters as shown on the right.
"tile"
"letter-green"
"letter-yellow"
"letter-gray"
When you generate each letter tile in your Wordle interfaces (see the
<span>
s above), define its "class"
as
"tile"
to set its size and font; further attach one of the
"letter-"
classes to color the letter.
In this assignment, you are going to implement the interface above as a dynamic web page. That is, your (Haskell) code will run and produce an HTML document that resembles the static HTML version above. To get a small taste of different aspects of web development, you’ll do this two ways: first as a client-side dynamic page and then as a server-side dynamic page.
In the first version, the user interface logic will run entirely on the client-side within the (JavaScript) environment in the browser.
We will use a a tasty Haskell front-end framework called Miso (v1.8.2.0) that exposes a simple MVC architecture to implement our Wordle application, which gets compiled to JavaScript via GHCJS.
❗ ❗ Note: GHCJS, and hence Miso, does not work with the latest version of Haskell. So, we will use a handy tool, called GHCup, that allows installing and running older versions of Haskell (see instructions below). ❗TRY IT OUT ASAP❗
Suggested Pairing: Looking for an easy and tasty miso dish? This miso cod is a family favorite.
Non-trivial web apps, of course, also need to do some server-side processing.
In a perfect world, all of the same libraries and tools would help with both client- and server-side concerns. Most of the mature Haskell web frameworks provide full-featured support for server-side development, but they do not provide a simple MVC architecture for client-side programming like Miso.
For our server-side adventure, we will use Happstack.Lite, which is nice and easy to get up and running. To get interactive behavior in our Wordle app (without writing JavaScript code, or somehow connecting to Miso-marinated Haskell), we will fake a pure MVC architecture in a very simple, very hacky way:
We will serialize (i.e. show
) the Model
as a URL parameter ("wordle.html?model=..."
), which our
server-side Haskell will retrieve before generating the appropriate HTML
to display based on that Model
.
For every user action that may be triggered on the client-side, our server-side Haskell will pre-compute the new model based on that action and the current model, and prepare a simple JavaScript event handler that redirects the browser to the page with the new model as a query parameter.
In this approach, there is no (non-trivial) JavaScript to write for our game; all the logic is written in Haskell run on the server-side. Were we doing do real web development, we would not structure our application like this. Nevertheless, this is a nice little toy approach for our assignment, and is a fine way to start trying out Happstack.Lite (and thus Happstack).
wordle-html/
(Static HTML and CSS files for the GUI
above)
wordle-miso/
(Version 1: Client-Side Page via Miso)
WordleMiso.hs
wordle-miso.cabal
wordle-happstack/
(Version 2: Server-Side Page via
Happstack.Lite)
WordleHappstack.hs
wordle-happstack.cabal
There is a nice tool called GHCup to manage the installation of updates to GHC and associated tools.
It is fun to imagine GHCup organizing different versions of the Haskell toolchain into separate cups. Thanks, Sam, for this delightful double entendre!
See what versions of GHC and related tools are installed on your machine:
% ghcup list
To get GHCJS and Miso to work, install the following older version:
% ghcup install ghc 8.10.7
To set which version of GHC is active:
% ghcup set ghc 8.10.7
[ Info ] GHC 8.10.7 successfully set as default version
Check that you are now running the older version:
% ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help
>
Note: GHC 8.10.7 uses language Haskell2010
,
which enables fewer extensions by default than GHC2021
.
If you want to use, for example, ExplicitForAll
and
TypeApplications
, you will need to update the
.cabal
file and/or define LANGUAGE
pragmas.
When you’re done working on the Miso project, remember to reset
things to the most recent version! You can do that by running
set
without a version number:
% ghcup set ghc
[ Info ] GHC 9.2.5 successfully set as default version
In addition to the commands above, you can also run the following (on Linux or Mac, but not Windows):
% ghcup tui
Check out this sweet terminal UI! Almost makes you want to say hello again to Hello, Again Wordle, doesn’t it?
When GHC 8.10.7 is set, try building (which will take a while the first time) and running:
% cabal build
% cabal run wordle-miso
You should now have a web server serving a page at http://localhost:8000/
.
Surf there.
WordleMiso.hs
)The starter code sets up the basic structure for your app, which you
should read through. Also peruse the documentation for the miso
and jsaddle-warp
packages as needed; there is a lot going on, but no need to understand
all of, or even much of, for our first tour through in this
assignment.
There is an Env
ironment type, which gets defined once
per server invocation, because the (static) CSS stylesheet and the list
of all words does not change across games (i.e. page loads).
data Env =
Env
{ getCss :: String
, getWordList :: [String]
}
In contrast, the Model
— which you may populate with
your PA 2 version to start, and then adjust, refactor, and polish as
appropriate — gets initialized each time the page is loaded (and then
updated during gameplay).
data Model =
Model
{ -- TODO
} deriving (Eq, Show)
To get your Wordle interface rendered and functioning, here’s a suggested progression of tasks.
Much of your work to render the UI will happen in:
viewModel :: Env -> Model -> View Action
Start by looking at the static index.html
file, and try
to generate the same elements dynamically. To start, the tiles can be
empty (no letters) and the buttons inactive. See: Miso.Html.
(I’m not sure about the difference between that and: Miso.Html.Element.)
For attribute values (e.g. id_
)
and text nodes (i.e. text
),
you have to explicitly convert String
s to MisoString
s,
via:
ms :: String -> MisoString
Consider enabling OverloadedStrings
to implicitly convert String
s to other string-like
types.
Then, for each button_
in the visual keyboard, attach an onClick
attribute to define what Action
should be generated when
the button is clicked. See: Miso.Html.Event.
(Notice that the starter code already connects keyboard events to
KeyPress
actions, via Miso.Subscription.Keyboard.)
Copy-paste your model-updating code from PA 2, and adjust to fit into the structure here.
Now, with current and previous guesses being tracked in the
Model
, render the guesses on the board.
Copy-paste-port your letter-coloring logic from PA 2. In this
setting, use the CSS classes letter-green
,
letter-yellow
, and letter-gray
to style the
tiles (via the class_
attribute).
Finally, copy-paste-port your visual keyboard summary logic from PA 2, again using the CSS classes to color the buttons.
This version is easier to get going. Remember to reset to the latest version of Haskell, and then build and run:
% ghcup set ghc
% cabal run wordle-happstack
This should start serving http://localhost:8000/wordle.html
.
Notice that, unlike the Miso version, this time
“wordle.html
” is in the URL.
WordleHappstack.hs
)The starter code sets up a basic application, similar to the Miso
version. Read through to get a sense of how it works. Start perusing the
documentation for the happstack-lite
package.
One difference to note is that, in this version, the answer word is
stored in Env
rather than Model
. The types and
structure of this app are more complicated than in the client-side
version. If you want to figure out how to pick a different answer every
time the page is loaded, rather than every time the server is launched,
go for it!
The suggested workflow of tasks is the same:
Having already completed the Miso version, the primary work in this
version is that in Happstack and Happstack.Lite, HTML drawing is
achieved using the blaze-html
package. Here’s a tutorial that
demonstrates the API. And here are a few of the relevant modules and
functions:
toHtml
pack
, unpack
The onclick
handlers for each button
should call updateAndRedirect
, which reloads the page with
the updated model as the new URL query parameter.
Note that our Happstack version does not support keyboard events; read the starter code for a brief discussion of why not. (If you’re feeling ambitious, add support for keyboard events.)
Again, considering using OverloadedStrings
to implicitly
convert String
s as appropriate.