├── src
├── static
│ ├── styles
│ │ ├── vars.scss
│ │ ├── components
│ │ │ ├── counter.scss
│ │ │ ├── hangman.scss
│ │ │ ├── index.scss
│ │ │ ├── box.scss
│ │ │ ├── header.scss
│ │ │ ├── keyboard.scss
│ │ │ ├── lost.scss
│ │ │ └── won.scss
│ │ └── main.scss
│ ├── index.js
│ └── index.html
├── favicon.ico
└── elm
│ ├── Actions.elm
│ ├── Main.elm
│ ├── Components
│ ├── BoxList.elm
│ ├── Header.elm
│ ├── Keyboard.elm
│ └── Hangman.elm
│ ├── Model.elm
│ ├── SentencesBank.elm
│ ├── View.elm
│ └── Update.elm
├── .gitignore
├── README.md
├── elm-package.json
├── package.json
└── webpack.config.js
/src/static/styles/vars.scss:
--------------------------------------------------------------------------------
1 | $primary: #00ca25;
--------------------------------------------------------------------------------
/src/static/styles/components/counter.scss:
--------------------------------------------------------------------------------
1 | .counter{
2 |
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | elm-stuff
2 | node_modules
3 | tmp/
4 | dist/
5 | yarn.lock
6 |
7 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/puemos/elm-hangman/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/static/styles/components/hangman.scss:
--------------------------------------------------------------------------------
1 | .hangman{
2 | padding: 20px;
3 | zoom: 2;
4 | }
--------------------------------------------------------------------------------
/src/elm/Actions.elm:
--------------------------------------------------------------------------------
1 | module Actions exposing (Action(..))
2 |
3 | type Action
4 | = Init
5 | | Press Int
6 | | Next
7 | | Prev
8 | | Noop
--------------------------------------------------------------------------------
/src/static/styles/components/index.scss:
--------------------------------------------------------------------------------
1 | @import './header';
2 | @import './keyboard';
3 | @import './box';
4 | @import './counter';
5 | @import './hangman';
6 | @import './lost';
7 | @import './won';
--------------------------------------------------------------------------------
/src/static/styles/components/box.scss:
--------------------------------------------------------------------------------
1 | .box {
2 | & + .box {
3 | margin-left: 15px;
4 | }
5 | }
6 | .boxWord {
7 | & + .boxWord {
8 | margin-left: 40px;
9 | }
10 | }
11 | .boxList {
12 | padding: 0 20px;
13 | }
14 |
--------------------------------------------------------------------------------
/src/static/styles/components/header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: 10px 20px;
3 | }
4 |
5 | .header-nav {
6 | button {
7 | font-family: 'Press Start 2P';
8 | color: white;
9 | border: none;
10 | background: transparent;
11 | margin: 0 20px;
12 | outline: none;
13 | &:active{
14 | opacity: 0.3;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/static/index.js:
--------------------------------------------------------------------------------
1 | // pull in desired CSS/SASS files
2 | require( './styles/main.scss' );
3 | var $ = jQuery = require( '../../node_modules/jquery/dist/jquery.js' ); // <--- remove if jQuery not needed
4 | require( '../../node_modules/bootstrap-sass/assets/javascripts/bootstrap.js' ); // <--- remove if Bootstrap's JS not needed
5 |
6 | // inject bundled Elm app into div#main
7 | var Elm = require( '../elm/Main' );
8 | Elm.Main.embed( document.getElementById( 'main' ) );
9 |
--------------------------------------------------------------------------------
/src/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Elm Hangman
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/static/styles/main.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Press+Start+2P');
2 | @import url('https://rawcdn.githack.com/kristoferjoseph/flexboxgrid/7.0/flexboxgrid.min.css');
3 | @import './vars';
4 | @import './components/index';
5 |
6 | html{
7 | font-family: 'Press Start 2P';
8 | }
9 |
10 | .app{
11 | background-color: rgb(120,120,120);
12 | color: white;
13 | height: 100vh;
14 | align-items: space-between;
15 | }
16 |
17 | .github-star{
18 | position: fixed;
19 | bottom: 0;
20 | left: 0;
21 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Hangman in Elm 🕴️ - [Try it now!](http://puemos.github.io/elm-hangman)
3 |
4 | 
5 |
6 | >Hangman is a paper and pencil guessing game for two or more players. One player thinks of a word, phrase or sentence and the other tries to guess it by suggesting letters or numbers, within a certain number of guesses.
7 |
8 | You can use the keyboard or just click the letters
9 |
10 | ## License
11 |
12 | MIT @ [Shy Alter](https://github.com/puemos)
13 |
--------------------------------------------------------------------------------
/src/elm/Main.elm:
--------------------------------------------------------------------------------
1 | module Main exposing (..)
2 |
3 | import Model exposing (Model)
4 | import Update
5 | import View
6 | import Keyboard exposing (KeyCode)
7 | import Html
8 | import Actions exposing (Action(..))
9 | import Task exposing (Task)
10 |
11 |
12 | subscriptions : Model -> Sub Action
13 | subscriptions model =
14 | Sub.batch
15 | [ Keyboard.downs Press
16 | ]
17 |
18 |
19 | main : Program Never Model Action
20 | main =
21 | Html.program
22 | { init = ( Model.initial, Task.perform (always Init) (Task.succeed 0) )
23 | , update = Update.update
24 | , view = View.view
25 | , subscriptions = subscriptions
26 | }
27 |
--------------------------------------------------------------------------------
/src/elm/Components/BoxList.elm:
--------------------------------------------------------------------------------
1 | module Components.BoxList exposing (boxList)
2 |
3 | import Html exposing (div, Html, text, button)
4 | import Html.Attributes exposing (style, class)
5 | import Actions exposing (..)
6 | import Char
7 |
8 |
9 | box : Char -> Html Action
10 | box letter =
11 | div
12 | [ class "box" ]
13 | [ text (String.fromChar (Char.toUpper letter)) ]
14 |
15 |
16 | boxWord : String -> Html Action
17 | boxWord word =
18 | div
19 | [ class "boxWord row" ]
20 | (List.map
21 | box
22 | (String.toList word)
23 | )
24 |
25 |
26 | boxList : String -> Html Action
27 | boxList sentencePos =
28 | div
29 | [ class "boxList row center middle last-sm" ]
30 | (List.map
31 | boxWord
32 | (String.words sentencePos)
33 | )
34 |
--------------------------------------------------------------------------------
/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "Example using `elm-webpack-loader`.",
4 | "repository": "https://github.com/moarwick/elm-webpack-starter.git",
5 | "license": "MIT",
6 | "source-directories": [
7 | "src/elm"
8 | ],
9 | "exposed-modules": [],
10 | "dependencies": {
11 | "elm-lang/animation-frame": "1.0.1 <= v < 2.0.0",
12 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
13 | "elm-lang/html": "2.0.0 <= v < 3.0.0",
14 | "elm-lang/http": "1.0.0 <= v < 2.0.0",
15 | "elm-lang/keyboard": "1.0.1 <= v < 2.0.0",
16 | "elm-lang/mouse": "1.0.1 <= v < 2.0.0",
17 | "elm-lang/svg": "2.0.0 <= v < 3.0.0",
18 | "evancz/elm-graphics": "1.0.1 <= v < 2.0.0",
19 | "evancz/elm-markdown": "3.0.1 <= v < 4.0.0"
20 | },
21 | "elm-version": "0.18.0 <= v < 0.19.0"
22 | }
23 |
--------------------------------------------------------------------------------
/src/static/styles/components/keyboard.scss:
--------------------------------------------------------------------------------
1 | .keyboard {
2 | padding-bottom: 20px;
3 | }
4 | .keyboardButton {
5 | position: relative;
6 | color: rgb(0, 0, 0);
7 | user-select: none;
8 | touch-action: pan-y;
9 | -webkit-user-drag: none;
10 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
11 | text-align: center;
12 | margin: 5px;
13 | width: 20px;
14 | height: 20px;
15 | line-height: 20px;
16 |
17 | cursor: pointer;
18 | }
19 |
20 | .keyboardButton__disabled {
21 | &::before,
22 | &::after {
23 | position: absolute;
24 | content: '';
25 | background: red;
26 | display: block;
27 | width: 100%;
28 | height: 2px;
29 | -webkit-transform: rotate(-45deg);
30 | transform: rotate(-45deg);
31 | left: 0;
32 | right: 0;
33 | top: 0;
34 | bottom: 0;
35 | margin: auto;
36 | }
37 | &::after {
38 | -webkit-transform: rotate(45deg);
39 | transform: rotate(45deg);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/elm/Components/Header.elm:
--------------------------------------------------------------------------------
1 | module Components.Header exposing (header)
2 |
3 | import Html exposing (div, Html, text, button)
4 | import Html.Events exposing (onClick)
5 | import Html.Attributes exposing (style, class)
6 | import Actions exposing (..)
7 |
8 |
9 | counter : Int -> Html Action
10 | counter strikes =
11 | div [ class "counter" ]
12 | [ text ("strikes: " ++ (toString strikes))
13 | ]
14 |
15 |
16 | header : Int -> Int -> Int -> Html Action
17 | header currPos maxPos strikes =
18 | div [ class "header row between" ]
19 | [ text "Hangman"
20 | , div [ class "header-nav row between middle"]
21 | [ button [ onClick Prev ] [ text "<" ]
22 | , div []
23 | [ text
24 | ((toString currPos) ++ "/" ++ (toString maxPos))
25 | ]
26 | , button [ onClick Next ] [ text ">" ]
27 | ]
28 | , counter strikes
29 | ]
30 |
--------------------------------------------------------------------------------
/src/elm/Model.elm:
--------------------------------------------------------------------------------
1 | module Model exposing (Status(..), Model, initial)
2 |
3 | import Set exposing (Set)
4 | import Char exposing (..)
5 | import Array exposing (Array, fromList)
6 | import SentencesBank exposing (bank)
7 |
8 |
9 | type Status
10 | = Play
11 | | Won
12 | | Lost
13 |
14 |
15 | type alias Model =
16 | { pressed : Set KeyCode
17 | , sentencePos : Int
18 | , sentencePossBank : Array String
19 | , answer : String
20 | , letters : List ( KeyCode, Bool )
21 | , strikes : Int
22 | , status : Status
23 | }
24 |
25 |
26 | abc : List Char
27 | abc =
28 | (String.toList "abcdefghijklmnopqrstuvwxyz")
29 |
30 |
31 | toUpperCode : Char -> KeyCode
32 | toUpperCode char =
33 | Char.toCode (Char.toUpper char)
34 |
35 |
36 | initial : Model
37 | initial =
38 | { pressed = Set.empty
39 | , sentencePos = 0
40 | , sentencePossBank = fromList bank
41 | , answer = ""
42 | , letters = List.map (\c -> ( (toUpperCode c), False )) abc
43 | , strikes = 0
44 | , status = Play
45 | }
46 |
--------------------------------------------------------------------------------
/src/elm/SentencesBank.elm:
--------------------------------------------------------------------------------
1 | module SentencesBank exposing (bank)
2 |
3 |
4 | bank : List String
5 | bank =
6 | [ "Awkward"
7 | , "Bagpipes"
8 | , "Banjo"
9 | , "Bungler"
10 | , "Croquet"
11 | , "Crypt"
12 | , "Dwarves"
13 | , "Fervid"
14 | , "Fishhook"
15 | , "Fjord"
16 | , "Gazebo"
17 | , "Gypsy"
18 | , "Haiku"
19 | , "Haphazard"
20 | , "Hyphen"
21 | , "Ivory"
22 | , "Jazzy"
23 | , "Jiffy"
24 | , "Jinx"
25 | , "Jukebox"
26 | , "Kayak"
27 | , "Kiosk"
28 | , "Klutz"
29 | , "Memento"
30 | , "Mystify"
31 | , "Numbskull"
32 | , "Ostracize"
33 | , "Oxygen"
34 | , "Pajama"
35 | , "Phlegm"
36 | , "Pixel"
37 | , "Polka"
38 | , "Quad"
39 | , "Quip"
40 | , "Rhythmic"
41 | , "Rogue"
42 | , "Sphinx"
43 | , "Squawk"
44 | , "Swivel"
45 | , "Toady"
46 | , "Twelfth"
47 | , "Unzip"
48 | , "Waxy"
49 | , "Wildebeest"
50 | , "Yacht"
51 | , "Zealous"
52 | , "Zigzag"
53 | , "Zippy"
54 | , "Zombie"
55 | ]
56 |
--------------------------------------------------------------------------------
/src/static/styles/components/lost.scss:
--------------------------------------------------------------------------------
1 | .lost {
2 | position: fixed;
3 | z-index: 1;
4 | width: 100vw;
5 | height: 100vh;
6 | transition: 1s cubic-bezier(0.215, 0.610, 0.355, 1);
7 | transition-property: opacity;
8 | opacity: 1;
9 | background: rgba(255, 138, 138, 0.94);
10 |
11 | &.transparent {
12 | z-index: -1;
13 | opacity: 0;
14 | }
15 | }
16 |
17 | .lost_caption {
18 | margin-bottom: 100px;
19 | font-family: 'Press Start 2P';
20 | font-size: 27px;
21 | color: white;
22 | }
23 |
24 | .lost_actions {
25 | button {
26 | padding: 10px 20px;
27 | font-family: 'Press Start 2P';
28 | font-size: 10px;
29 | transition: 1s cubic-bezier(0.215, 0.610, 0.355, 1);
30 | transition-property: background color;
31 | color: white;
32 | border: 1px solid white;
33 | outline: none;
34 | background: transparent;
35 | &:hover {
36 | color: rgba(255, 138, 138, 1);
37 | background: white;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/static/styles/components/won.scss:
--------------------------------------------------------------------------------
1 | .won {
2 | position: fixed;
3 | z-index: 1;
4 | width: 100vw;
5 | height: 100vh;
6 | transition: 1s cubic-bezier(0.215, 0.610, 0.355, 1);
7 | transition-property: opacity;
8 | opacity: 1;
9 | background: rgba(105, 251, 130, 0.94);
10 | &.transparent {
11 | z-index: -1;
12 | width: 0;
13 | opacity: 0;
14 | }
15 | }
16 | .won_caption {
17 | margin-bottom: 100px;
18 | font-family: 'Press Start 2P';
19 | font-size: 27px;
20 | color: white;
21 | }
22 | .won_actions {
23 | button {
24 | padding: 10px 20px;
25 | font-family: 'Press Start 2P';
26 | font-size: 10px;
27 | transition: 1s cubic-bezier(0.215, 0.610, 0.355, 1);
28 | transition-property: background color;
29 | color: white;
30 | border: 1px solid white;
31 | outline: none;
32 | background: transparent;
33 | & + button {
34 | margin-left: 40px;
35 | }
36 | &:hover {
37 | color: rgba(105, 251, 130, 1);
38 | background: white;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/elm/Components/Keyboard.elm:
--------------------------------------------------------------------------------
1 | module Components.Keyboard exposing (keyboard)
2 |
3 | import Html exposing (div, Html, text, button)
4 | import Html.Events exposing (onClick)
5 | import Html.Attributes exposing (style, class)
6 | import Actions exposing (..)
7 | import Char
8 |
9 |
10 | keyboardButton : Bool -> ( Int, Bool ) -> Html Action
11 | keyboardButton disable ( charCode, isPreesed ) =
12 | let
13 | className =
14 | if isPreesed then
15 | "keyboardButton__disabled"
16 | else
17 | ""
18 |
19 | letter =
20 | (String.fromChar (Char.toUpper (Char.fromCode charCode)))
21 |
22 | action =
23 | if (disable) then
24 | Noop
25 | else
26 | (Press charCode)
27 | in
28 | div
29 | [ class ("keyboardButton " ++ className), onClick action ]
30 | [ text letter ]
31 |
32 |
33 | keyboard : Bool -> List ( Int, Bool ) -> Html Action
34 | keyboard disable letters =
35 | div
36 | [ class "keyboard row center" ]
37 | (List.map
38 | (keyboardButton disable)
39 | letters
40 | )
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elm-hangman",
3 | "description": "The game of Hangman written in Elm.",
4 | "version": "0.2.0",
5 | "license": "MIT",
6 | "author": "Shy Alter",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/puemos/elm-hangman"
10 | },
11 | "keywords": [
12 | "elm",
13 | "hangman",
14 | "webpack",
15 | "game"
16 | ],
17 | "scripts": {
18 | "start": "webpack-dev-server --hot --inline",
19 | "prebuild": "rimraf dist",
20 | "build": "webpack",
21 | "reinstall": "npm i rimraf && rimraf node_modules && npm uninstall -g elm && npm i -g elm && npm i && elm package install"
22 | },
23 | "devDependencies": {
24 | "autoprefixer": "^6.7.7",
25 | "bootstrap-sass": "^3.4.1",
26 | "copy-webpack-plugin": "^4.0.1",
27 | "css-loader": "^0.27.3",
28 | "elm": "^0.18.0",
29 | "elm-webpack-loader": "^4.3.0",
30 | "extract-text-webpack-plugin": "^2.1.0",
31 | "file-loader": "^0.10.1",
32 | "html-webpack-plugin": "^2.28.0",
33 | "jquery": "^3.5.0",
34 | "node-sass": "^7.0.0",
35 | "postcss-loader": "^1.3.3",
36 | "rimraf": "^2.6.1",
37 | "sass-loader": "^6.0.3",
38 | "style-loader": "^0.16.0",
39 | "url-loader": "^0.5.8",
40 | "webpack": "^2.3.1",
41 | "webpack-dev-server": "^5.0.4",
42 | "webpack-merge": "^4.1.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/elm/View.elm:
--------------------------------------------------------------------------------
1 | module View exposing (view)
2 |
3 | import Html exposing (div, Html, text, button, iframe)
4 | import Html.Attributes exposing (..)
5 | import Model exposing (Model, Status)
6 | import Components.Hangman exposing (hangman)
7 | import Components.Header exposing (header)
8 | import Components.Keyboard exposing (keyboard)
9 | import Components.BoxList exposing (boxList)
10 | import Html.Events exposing (onClick)
11 | import Actions exposing (..)
12 | import Array
13 |
14 |
15 | (=>) : a -> b -> ( a, b )
16 | (=>) =
17 | (,)
18 |
19 |
20 | renderLostScreen : Bool -> Html Action
21 | renderLostScreen lost =
22 | let
23 | className =
24 | if lost then
25 | "lost column middle center"
26 | else
27 | "lost transparent column middle center"
28 | in
29 | div [ class className ]
30 | [ div [ class "lost_caption" ] [ text "You died" ]
31 | , div [ class "lost_actions" ]
32 | [ button [ onClick Init ] [ text "Replay" ]
33 | ]
34 | ]
35 |
36 |
37 | renderWonScreen : Bool -> Html Action
38 | renderWonScreen won =
39 | let
40 | className =
41 | if won then
42 | "won column middle center"
43 | else
44 | "won transparent column middle center"
45 | in
46 | div [ class className ]
47 | [ div [ class "won_caption" ] [ text "Saved!" ]
48 | , div [ class "won_actions row" ]
49 | [ button [ onClick Init ] [ text "Replay" ]
50 | , button [ onClick Next ] [ text "Next" ]
51 | ]
52 | ]
53 |
54 |
55 | view : Model -> Html Action
56 | view model =
57 | let
58 | sentencePosBankLength =
59 | Array.length model.sentencePossBank
60 | in
61 | div
62 | [ class "app column between" ]
63 | [ header model.sentencePos sentencePosBankLength model.strikes
64 | , renderLostScreen (model.status == Model.Lost)
65 | , renderWonScreen (model.status == Model.Won)
66 | , div [ class "main column around" ]
67 | [ div [ class "hangman row middle center" ]
68 | [ hangman model.strikes ]
69 | , boxList model.answer
70 | ]
71 | , keyboard (model.status == Model.Lost) model.letters
72 | , iframe [ class "github", src "https://ghbtns.com/github-btn.html?user=puemos&repo=elm-hangman&type=star&v=2", attribute "frameborder" "0", attribute "scrolling" "0", attribute "width" "170px", attribute "height" "20px" ] []
73 | ]
74 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var merge = require('webpack-merge');
4 | var HtmlWebpackPlugin = require('html-webpack-plugin');
5 | var autoprefixer = require('autoprefixer');
6 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
7 | var CopyWebpackPlugin = require('copy-webpack-plugin');
8 |
9 |
10 | const prod = 'production';
11 | const dev = 'development';
12 |
13 | // determine build env
14 | const TARGET_ENV = process.env.npm_lifecycle_event === 'build' ? prod : dev;
15 | const isDev = TARGET_ENV == dev;
16 | const isProd = TARGET_ENV == prod;
17 |
18 | // entry and output path/filename variables
19 | const entryPath = path.join(__dirname, 'src/static/index.js');
20 | const outputPath = path.join(__dirname, 'dist');
21 | const outputFilename = isProd ? '[name]-[hash].js' : '[name].js'
22 |
23 | console.log('WEBPACK GO! Building for ' + TARGET_ENV);
24 |
25 | // common webpack config (valid for dev and prod)
26 | var commonConfig = {
27 | output: {
28 | path: outputPath,
29 | filename: `static/js/${outputFilename}`,
30 | },
31 | resolve: {
32 | extensions: ['.js', '.elm'],
33 | modules: ['node_modules']
34 | },
35 | module: {
36 | noParse: /\.elm$/,
37 | rules: [{
38 | test: /\.(eot|ttf|woff|woff2|svg)$/,
39 | use: 'file-loader?publicPath=../../&name=static/css/[hash].[ext]'
40 | }]
41 | },
42 | plugins: [
43 | new webpack.LoaderOptionsPlugin({
44 | options: {
45 | postcss: [autoprefixer()]
46 | }
47 | }),
48 | new HtmlWebpackPlugin({
49 | template: 'src/static/index.html',
50 | inject: 'body',
51 | favicon: 'src/favicon.ico',
52 | filename: 'index.html'
53 | })
54 | ]
55 | }
56 |
57 | // additional webpack settings for local env (when invoked by 'npm start')
58 | if (isDev === true) {
59 | module.exports = merge(commonConfig, {
60 | entry: [
61 | 'webpack-dev-server/client?http://localhost:8080',
62 | entryPath
63 | ],
64 | devServer: {
65 | // serve index.html in place of 404 responses
66 | historyApiFallback: true,
67 | contentBase: './src',
68 | hot: true
69 | },
70 | module: {
71 | rules: [{
72 | test: /\.elm$/,
73 | exclude: [/elm-stuff/, /node_modules/],
74 | use: [{
75 | loader: 'elm-webpack-loader',
76 | options: {
77 | verbose: true,
78 | warn: true,
79 | debug: true
80 | }
81 | }]
82 | },{
83 | test: /\.sc?ss$/,
84 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
85 | }]
86 | }
87 | });
88 | }
89 |
90 | // additional webpack settings for prod env (when invoked via 'npm run build')
91 | if (isProd === true) {
92 | module.exports = merge(commonConfig, {
93 | entry: entryPath,
94 | module: {
95 | rules: [{
96 | test: /\.elm$/,
97 | exclude: [/elm-stuff/, /node_modules/],
98 | use: 'elm-webpack-loader'
99 | }, {
100 | test: /\.sc?ss$/,
101 | use: ExtractTextPlugin.extract({
102 | fallback: 'style-loader',
103 | use: ['css-loader', 'postcss-loader', 'sass-loader']
104 | })
105 | }]
106 | },
107 | plugins: [
108 | new ExtractTextPlugin({
109 | filename: 'static/css/[name]-[hash].css',
110 | allChunks: true,
111 | }),
112 | new CopyWebpackPlugin([{
113 | from: 'src/static/img/',
114 | to: 'static/img/'
115 | }, {
116 | from: 'src/favicon.ico'
117 | }]),
118 |
119 | // extract CSS into a separate file
120 | // minify & mangle JS/CSS
121 | new webpack.optimize.UglifyJsPlugin({
122 | minimize: true,
123 | compressor: {
124 | warnings: false
125 | }
126 | // mangle: true
127 | })
128 | ]
129 | });
130 | }
131 |
--------------------------------------------------------------------------------
/src/elm/Components/Hangman.elm:
--------------------------------------------------------------------------------
1 | module Components.Hangman exposing (hangman)
2 |
3 | import Actions exposing (..)
4 | import Svg exposing (..)
5 | import Svg.Attributes exposing (..)
6 |
7 |
8 | toShow : Int -> Int -> String
9 | toShow n strikes =
10 | if n > strikes then
11 | "hidden"
12 | else
13 | ""
14 |
15 |
16 | hangman : Int -> Svg Action
17 | hangman strikes =
18 | svg [ height "135", width "81" ]
19 | [ node "title"
20 | []
21 | [ text "Layer 1" ]
22 | , g [ id "base", visibility (toShow 1 strikes) ]
23 | [ node "line"
24 | [ fill "none", id "svg_1", stroke "#000", strokeWidth "5", x1 "11.1835", x2 "70.00333", y1 "123.35251", y2 "123.35251" ]
25 | []
26 | , text " "
27 | ]
28 | , g [ id "poll", visibility (toShow 2 strikes) ]
29 | [ node "line"
30 | [ fill "none", id "svg_5", stroke "#000", strokeWidth "5", transform "rotate(90 56.118484497070305,64.44848632812501) ", x1 "-3.88497", x2 "116.12197", y1 "64.44849", y2 "64.44849" ]
31 | []
32 | , text " "
33 | ]
34 | , g [ id "support", visibility (toShow 3 strikes) ]
35 | [ node "line"
36 | [ fill "none", id "svg_6", stroke "#000", strokeWidth "5", transform "rotate(-141 44.18540954589844,15.687820434570312) ", x1 "29.20883", x2 "59.162", y1 "15.68777", y2 "15.68777" ]
37 | []
38 | , text " "
39 | ]
40 | , g [ id "above", visibility (toShow 4 strikes) ]
41 | [ node "line"
42 | [ fill "none", id "svg_7", stroke "#000", strokeWidth "5", x1 "16.55079", x2 "57.15915", y1 "6.96102", y2 "6.96102" ]
43 | []
44 | , text " "
45 | ]
46 | , g [ id "rope", visibility (toShow 5 strikes) ]
47 | [ node "rect"
48 | [ fill "#999999", height "2.46019", id "svg_25", strokeWidth "5", transform "rotate(90 21.00877380371094,29.422645568847656) ", width "40.77304", x "0.62226", y "28.19256" ]
49 | []
50 | , text " "
51 | , node "rect"
52 | [ fill "#999999", height "2.46019", id "svg_13", strokeWidth "5", width "11.59903", x "12.57304", y "48.2277" ]
53 | []
54 | , text " "
55 | ]
56 | , g [ id "head", visibility (toShow 6 strikes) ]
57 | [ node "rect"
58 | [ fill "#000000", height "11.59903", id "svg_8", strokeWidth "5", width "11.59903", x "12.57304", y "36.6284" ]
59 | []
60 | , text " "
61 | , node "rect"
62 | [ fill "#ffffff", height "3.69068", id "svg_26", strokeWidth "5", width "3.69068", x "12.44894", y "36.59328" ]
63 | []
64 | , text " "
65 | , node "rect"
66 | [ fill "#ffffff", height "3.69068", id "svg_27", strokeWidth "5", width "3.69068", x "20.5333", y "36.59328" ]
67 | []
68 | , text " "
69 | ]
70 | , g [ id "body", visibility (toShow 7 strikes) ]
71 | [ node "rect"
72 | [ fill "#000000", height "23.90158", id "svg_14", strokeWidth "5", width "18.80492", x "9.10975", y "50.65303" ]
73 | []
74 | , text " "
75 | ]
76 | , g [ id "left-arm", visibility (toShow 8 strikes) ]
77 | [ node "rect"
78 | [ fill "#000000", height "3.86643", id "svg_17", strokeWidth "5", width "3.86643", x "5.24331", y "74.20312" ]
79 | []
80 | , text " "
81 | ]
82 | , g [ id "right-arm", visibility (toShow 9 strikes) ]
83 | [ node "rect"
84 | [ fill "#000000", height "3.86643", id "svg_18", strokeWidth "5", width "3.86643", x "27.73892", y "74.55462" ]
85 | []
86 | , text " "
87 | ]
88 | , g [ id "left-foot1", visibility (toShow 10 strikes) ]
89 | [ node "rect"
90 | [ fill "#000000", height "3.33919", id "svg_19", strokeWidth "5", width "12.12654", x "12.44893", y "74.73036" ]
91 | []
92 | , text " "
93 | , node "rect"
94 | [ fill "#000000", height "3.33919", id "svg_21", strokeWidth "5", transform "rotate(90 14.11854553222655,87.38415527343751) ", width "18.62917", x "4.80395", y "85.71457" ]
95 | []
96 | , text " "
97 | , node "rect"
98 | [ fill "#000000", height "3.86643", id "svg_23", strokeWidth "5", width "3.86643", x "8.75825", y "96.52298" ]
99 | []
100 | , text " "
101 | ]
102 | , g [ id "right-foot", visibility (toShow 11 strikes) ]
103 | [ node "rect"
104 | [ fill "#000000", height "3.33919", id "svg_22", strokeWidth "5", transform "rotate(90 22.905868530273427,87.38415527343751) ", width "18.62917", x "13.59128", y "85.71456" ]
105 | []
106 | , text " "
107 | , node "rect"
108 | [ fill "#000000", height "3.86643", id "svg_24", strokeWidth "5", width "3.86643", x "24.57547", y "96.69873" ]
109 | []
110 | , text " "
111 | ]
112 | ]
113 |
--------------------------------------------------------------------------------
/src/elm/Update.elm:
--------------------------------------------------------------------------------
1 | module Update exposing (update)
2 |
3 | import Set exposing (..)
4 | import Model exposing (..)
5 | import Actions exposing (..)
6 | import Char exposing (..)
7 | import Array exposing (Array)
8 |
9 |
10 | nextKeyCode : Char.KeyCode
11 | nextKeyCode =
12 | 39
13 |
14 |
15 | prevKeyCode : Char.KeyCode
16 | prevKeyCode =
17 | 37
18 |
19 |
20 | maxStrikes : Int
21 | maxStrikes =
22 | 11
23 |
24 |
25 | update : Action -> Model -> ( Model, Cmd Action )
26 | update action model =
27 | case action of
28 | Init ->
29 | ( loadSentence model.sentencePos model, Cmd.none )
30 |
31 | Next ->
32 | ( onNext model, Cmd.none )
33 |
34 | Prev ->
35 | ( onPrev model, Cmd.none )
36 |
37 | Press char ->
38 | let
39 | normalizeChar =
40 | Char.toCode (Char.toUpper (Char.fromCode char))
41 |
42 | maybeSentence =
43 | Array.get model.sentencePos model.sentencePossBank
44 |
45 | sentence =
46 | case maybeSentence of
47 | Just sentenceString ->
48 | sentenceString
49 |
50 | Nothing ->
51 | ("")
52 |
53 | nextPressed =
54 | onKeyPress char model.pressed
55 |
56 | nextStrikes =
57 | calcStrikes nextPressed sentence
58 |
59 | nextAnswer =
60 | checkSentence nextPressed sentence
61 |
62 | nextLetters =
63 | (calcLetters nextPressed model.letters)
64 |
65 | nextStatus =
66 | if (nextStrikes >= maxStrikes) then
67 | Lost
68 | else if (nextAnswer == sentence) then
69 | Won
70 | else
71 | model.status
72 | in
73 | if (char == nextKeyCode) then
74 | ( onNext model, Cmd.none )
75 | else if (char == prevKeyCode) then
76 | ( onPrev model, Cmd.none )
77 | else if (model.status == Play) then
78 | ( { model
79 | | pressed = nextPressed
80 | , strikes = nextStrikes
81 | , answer = nextAnswer
82 | , letters = nextLetters
83 | , status = nextStatus
84 | }
85 | , Cmd.none
86 | )
87 | else
88 | ( model, Cmd.none )
89 |
90 | Noop ->
91 | ( model, Cmd.none )
92 |
93 |
94 | onKeyPress : KeyCode -> Set KeyCode -> Set KeyCode
95 | onKeyPress current pressed =
96 | let
97 | char =
98 | (Char.fromCode current)
99 | in
100 | if (Char.isLower char || Char.isUpper char) then
101 | Set.insert current pressed
102 | else
103 | pressed
104 |
105 |
106 | loadSentence : Int -> Model -> Model
107 | loadSentence pos model =
108 | let
109 | intiModel =
110 | Model.initial
111 |
112 | maybeSentence =
113 | Array.get pos model.sentencePossBank
114 |
115 | sentencePos =
116 | case maybeSentence of
117 | Just sentencePosString ->
118 | sentencePosString
119 |
120 | Nothing ->
121 | ("")
122 |
123 | nextPressed =
124 | onKeyPress 0 intiModel.pressed
125 |
126 | nextAnswer =
127 | checkSentence nextPressed sentencePos
128 | in
129 | ({ intiModel
130 | | answer = nextAnswer
131 | , sentencePos = pos
132 | }
133 | )
134 |
135 |
136 | onNext : Model -> Model
137 | onNext model =
138 | let
139 | sentencePosBankLength =
140 | Array.length model.sentencePossBank
141 | in
142 | (loadSentence (min (model.sentencePos + 1) (sentencePosBankLength - 1))) model
143 |
144 |
145 | onPrev : Model -> Model
146 | onPrev model =
147 | loadSentence (max (model.sentencePos - 1) 0) model
148 |
149 |
150 | calcLetters : Set KeyCode -> List ( KeyCode, Bool ) -> List ( KeyCode, Bool )
151 | calcLetters pressed letters =
152 | let
153 | checkFn =
154 | (didPressed pressed)
155 | in
156 | List.map (\( c, _ ) -> ( c, checkFn (Char.fromCode c) )) letters
157 |
158 |
159 | calcStrikes : Set Int -> String -> Int
160 | calcStrikes pressed sentence =
161 | let
162 | checkFn =
163 | (didPressed pressed)
164 |
165 | sentenceCharList =
166 | (String.toList sentence)
167 |
168 | sentenceCharListNoSpace =
169 | (List.filter (\c -> c /= ' ') sentenceCharList)
170 |
171 | numOfCorrect =
172 | (Set.fromList (List.filter checkFn sentenceCharListNoSpace))
173 | in
174 | (Set.size pressed) - (Set.size numOfCorrect)
175 |
176 |
177 | didPressed : Set KeyCode -> Char -> Bool
178 | didPressed preesed letter =
179 | let
180 | char =
181 | (Char.toCode (Char.toUpper letter))
182 | in
183 | Set.member char preesed
184 |
185 |
186 | swapUnpresses : (Char -> Bool) -> Char -> Char
187 | swapUnpresses checkFn letter =
188 | if not (Char.isUpper (Char.toUpper letter) ) then
189 | letter
190 | else if (checkFn letter) then
191 | letter
192 | else
193 | '_'
194 |
195 |
196 | checkSentence : Set KeyCode -> String -> String
197 | checkSentence pressed sentencePos =
198 | let
199 | checkFn =
200 | (didPressed pressed)
201 |
202 | sentencePosCharList =
203 | (String.toList sentencePos)
204 | in
205 | String.fromList (List.map (swapUnpresses checkFn) sentencePosCharList)
206 |
--------------------------------------------------------------------------------