add elm for Awari

This commit is contained in:
Auryn Engel
2022-03-16 09:58:40 +01:00
parent 7da5cdb12b
commit c797d9655f
7 changed files with 719 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,visualstudiocode,elm
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,visualstudiocode,elm
### Elm ###
# elm-package generated files
elm-stuff
# elm-repl generated files
repl-temp-*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
# End of https://www.toptal.com/developers/gitignore/api/macos,visualstudiocode,elm
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node
app.js

View File

@@ -0,0 +1,12 @@
# Awari
This is an Elm implementation of the `Basic Compouter Games` Game Awari.
## Build App
- install elm
```bash
yarn
yarn build
```

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
</head>
<body>
<div id="elm-app-is-loaded-here" />
<script src="app.js"></script>
<script>
var app = Elm.Main.init({
node: document.getElementById("elm-app-is-loaded-here"),
flags: {}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,24 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0"
},
"indirect": {
"elm/json": "1.1.3",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "04_Awari",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"live": "elm-live src/Main.elm --proxy-prefix=/api --proxy-host=http://localhost:8080/api --open --start-page=resources/index.html -- --output=app.js --debug",
"build": "elm make src/Main.elm --optimize --output docs/app.js && cp -R resources/ docs/"
},
"devDependencies": {
"elm-live": "^4.0.2"
}
}

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
</head>
<body>
<div id="elm-app-is-loaded-here" />
<script src="app.js"></script>
<script>
var app = Elm.Main.init({
node: document.getElementById("elm-app-is-loaded-here"),
flags: {}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,424 @@
module Main exposing (main)
import Array exposing (Array, repeat)
import Browser
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (onClick)
-- Main
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- Model
type alias Model =
{ board : Array Int
, playerOnesTurn : Bool
, gameFinished : Bool
}
init : () -> ( Model, Cmd Msg )
init _ =
( { board = initArray 0 36 (repeat boardLength 0), playerOnesTurn = False, gameFinished = False }, Cmd.none )
-- Messages
type Msg
= BoardClicked Int
| Reset
-- Update
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
BoardClicked index ->
if indexIsOnPit index || index < rightPitIndex && model.playerOnesTurn || index > rightPitIndex && not model.playerOnesTurn then
( model, Cmd.none )
else
let
lastStoneInPitIndex =
lastStonePitPosition index model.board
boardAfterStonesSet =
changesOnBoardAfterStonesSet model.playerOnesTurn lastStoneInPitIndex (boardClicked index model.board)
nextPlayer =
determineNextPlayer lastStoneInPitIndex model.playerOnesTurn boardAfterStonesSet
in
( { model | board = boardAfterStonesSet, playerOnesTurn = nextPlayer, gameFinished = calculateGameFinished boardAfterStonesSet }, Cmd.none )
Reset ->
init ()
-- Constants
boardLength : Int
boardLength =
14
leftPitIndex : Int
leftPitIndex =
0
rightPitIndex : Int
rightPitIndex =
7
-- View
view : Model -> Html Msg
view model =
div [ style "margin" "4rem" ]
[ h1 [] [ text "Awari" ]
, viewDescription
, getPlayerTurnText False model.playerOnesTurn
, viewBoard model
, viewWinnerIfGameFinished model
, getPlayerTurnText True model.playerOnesTurn
, div
[ style "display" "flex"
, style "justify-content" "center"
]
[ button [ onClick Reset, style "max-width" "10rem", style "margin" "0", style "display" "inline-block" ] [ text "Restart" ] ]
]
getPlayerTurnText : Bool -> Bool -> Html msg
getPlayerTurnText playerOne playerOnesTurn =
if playerOne && playerOnesTurn || not playerOnesTurn && not playerOne then
div [ style "height" "2rem", style "margin" "1rem 0", style "font-size" "1.5rem" ]
[ text
("It's your turn Player "
++ (if playerOnesTurn then
" 2 "
else
" 1 "
)
)
]
else
div [ style "height" "2rem", style "margin" "1rem", style "font-size" "1.5rem" ] []
viewWinnerIfGameFinished : Model -> Html msg
viewWinnerIfGameFinished model =
if not model.gameFinished then
div [] []
else
div [] [ text ("Winner is" ++ winnerPlayer model) ]
winnerPlayer : Model -> String
winnerPlayer model =
if getLeftPitValue model.board > getRightPitValue model.board then
"Player 1"
else if getLeftPitValue model.board < getRightPitValue model.board then
"Player 2"
else
"None"
viewBoard : Model -> Html Msg
viewBoard model =
let
boardGenerator =
boardCard model.playerOnesTurn
in
div boardSyle
(Array.toList <|
Array.indexedMap boardGenerator model.board
)
boardSyle : List (Attribute msg)
boardSyle =
[ style "display" "flex"
, style "gap" "1rem"
, style "display" "grid"
, style "grid-template-columns" "repeat(8, 1fr)"
, style "align-items" "center"
, style "grid-template-areas" """
'a b c d e f g h'
'a n m l k j i h'
"""
]
boardCard : Bool -> Int -> Int -> Html Msg
boardCard playerOnesTurn index element =
div (boardCardStyle playerOnesTurn index) [ text (String.fromInt element) ]
boardCardStyle : Bool -> Int -> List (Attribute Msg)
boardCardStyle playerOnesTurn index =
if not playerOnesTurn && index < rightPitIndex || playerOnesTurn && index >= rightPitIndex then
[ onClick (BoardClicked index)
, style "grid-area" (areaFromIndex index)
, style "cursor" "pointer"
, style "background" "#36454F"
, style "border-radius" "6px"
, style "text-align" "center"
, style "padding" "0.5rem"
]
else
[ style "grid-area" (areaFromIndex index)
, style "background" "black"
, style "border-radius" "6px"
, style "text-align" "center"
, style "padding" "0.5rem"
]
viewDescription : Html msg
viewDescription =
details []
[ summary [] [ text "How the game works" ]
, p [] [ text """
Awari is an ancient African game played with seven sticks and thirty-six stones or beans laid out as shown above. The board is divided into six compartments or pits on each side. In addition, there are two special home pits at the ends.
A move is made by taking all the beans from any (non-empty) pit on your own side. Starting from the pit to the right of this one, these beans are sown one in each pit working around the board anticlockwise.
A turn consists of one or two moves. If the last bean of your move is sown in your own home you may take a second move.
If the last bean sown in a move lands in an empty pit, provided that the opposite pit is not empty, all the beans in the opposite pit, together with the last bean sown are captured and moved to the players home.
When either side is empty, the game is finished. The player with the most beans in his home has won.
""" ]
]
-- Functions
checkIfPlayerCanNotDoMove : Bool -> Array Int -> Bool
checkIfPlayerCanNotDoMove playerOnesTurn board =
if playerOnesTurn then
Array.foldr (+) 0 (Array.slice (rightPitIndex + 1) boardLength board) == 0
else
Array.foldr (+) 0 (Array.slice (leftPitIndex + 1) rightPitIndex board) == 0
determineNextPlayer : Int -> Bool -> Array Int -> Bool
determineNextPlayer index playerOnesTurn board =
if checkIfPlayerCanNotDoMove (not playerOnesTurn) board then
playerOnesTurn
else if indexIsOnPit index then
playerOnesTurn
else
not playerOnesTurn
indexIsOnPit : Int -> Bool
indexIsOnPit index =
index == leftPitIndex || index == rightPitIndex
calculateGameFinished : Array Int -> Bool
calculateGameFinished board =
Array.foldr (+) 0 board - getLeftPitValue board - getRightPitValue board == 0
getLeftPitValue : Array Int -> Int
getLeftPitValue board =
getPitValue leftPitIndex board
getRightPitValue : Array Int -> Int
getRightPitValue board =
getPitValue rightPitIndex board
getPitValue : Int -> Array Int -> Int
getPitValue index board =
Maybe.withDefault 0 (Array.get index board)
lastStonePitPosition : Int -> Array Int -> Int
lastStonePitPosition index board =
calcLastStonePosition index board (Maybe.withDefault 0 (Array.get index board))
calcLastStonePosition : Int -> Array Int -> Int -> Int
calcLastStonePosition index board stones =
if stones == 0 then
index
else
calcLastStonePosition (incrementRotatingIndex index board) board (stones - 1)
changesOnBoardAfterStonesSet : Bool -> Int -> Array Int -> Array Int
changesOnBoardAfterStonesSet playerOne lastStonesIndex board =
if Maybe.withDefault 0 (Array.get lastStonesIndex board) == 1 then
if indexIsOnPit lastStonesIndex then
board
else
let
sumStones =
Maybe.withDefault 0 (Array.get (boardLength - lastStonesIndex) board) + 1
currentPit =
if playerOne then
getRightPitValue board
else
getLeftPitValue board
baordWithIndexZero =
Array.set lastStonesIndex 0 board
baordWithOppositeZero =
Array.set (boardLength - lastStonesIndex) 0 baordWithIndexZero
boardWithPitSum =
if playerOne then
Array.set rightPitIndex (sumStones + currentPit) baordWithOppositeZero
else
Array.set leftPitIndex (sumStones + currentPit) baordWithOppositeZero
in
boardWithPitSum
else
board
initArray : Int -> Int -> Array Int -> Array Int
initArray index stones array =
if stones == 0 then
array
else if indexIsOnPit index then
initArray (index + 1) stones array
else
initArray (index + 1) (stones - 3) (Array.set index 3 array)
boardClicked : Int -> Array Int -> Array Int
boardClicked index board =
if indexIsOnPit index then
board
else
let
stonesLeft =
Maybe.withDefault 0 (Array.get index board)
startBoard =
Array.set index 0 board
in
placeStones (incrementRotatingIndex index startBoard) stonesLeft startBoard
incrementRotatingIndex : Int -> Array Int -> Int
incrementRotatingIndex currentIndex board =
if 0 == currentIndex then
Array.length board - 1
else
currentIndex - 1
placeStones : Int -> Int -> Array Int -> Array Int
placeStones index stonesLeft board =
if stonesLeft == 0 then
board
else
let
oldStones =
Maybe.withDefault 0 (Array.get index board)
in
placeStones (incrementRotatingIndex index board) (stonesLeft - 1) (Array.set index (oldStones + 1) board)
areaFromIndex : Int -> String
areaFromIndex index =
case index of
0 ->
"a"
1 ->
"b"
2 ->
"c"
3 ->
"d"
4 ->
"e"
5 ->
"f"
6 ->
"g"
7 ->
"h"
8 ->
"i"
9 ->
"j"
10 ->
"k"
11 ->
"l"
12 ->
"m"
13 ->
"n"
i ->
String.fromInt i