├── .github └── workflows │ ├── Makefile │ ├── build-and-test.jsonnet │ ├── build-and-test.yml │ ├── jsonnet_to_yaml.sh │ └── semgrep.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── core ├── Basics.ml ├── Color.ml ├── Keyboard.ml ├── Set.ml ├── Set_.ml ├── Set_.mli ├── Time.ml └── dune ├── docs ├── elm_core │ ├── Basics │ │ └── index.html │ ├── Color │ │ └── index.html │ ├── Keyboard │ │ └── index.html │ ├── Set │ │ └── index.html │ ├── Set_ │ │ └── index.html │ ├── Time │ │ └── index.html │ └── index.html ├── elm_playground │ ├── Playground │ │ └── index.html │ ├── Playground_platform │ │ └── index.html │ └── index.html ├── elm_playground_native │ └── index.html ├── elm_playground_web │ └── index.html ├── elm_system │ ├── Cmd │ │ └── index.html │ ├── Sub │ │ └── index.html │ └── index.html ├── examples │ ├── Animation.bc.js │ ├── Animation.html │ ├── Keyboard.bc.js │ ├── Keyboard.html │ ├── Mario.bc.js │ ├── Mario.html │ ├── Mouse.bc.js │ ├── Mouse.html │ ├── Picture.bc.js │ ├── Picture.html │ ├── Smiley.bc.js │ ├── Smiley.html │ ├── Turtle.bc.js │ ├── Turtle.html │ ├── Words.bc.js │ ├── Words.html │ └── index.html ├── games │ ├── Asteroid.bc.js │ ├── Asteroid.html │ ├── Pong.bc.js │ ├── Pong.html │ ├── Snake.bc.js │ ├── Snake.html │ ├── Template.html │ ├── Tetris.bc.js │ ├── Tetris.html │ └── index.html ├── index.html ├── odoc.support │ ├── fonts │ │ ├── KaTeX_AMS-Regular.woff2 │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ ├── KaTeX_Main-Bold.woff2 │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ ├── KaTeX_Main-Italic.woff2 │ │ ├── KaTeX_Main-Regular.woff2 │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ ├── KaTeX_Math-Italic.woff2 │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ ├── KaTeX_Script-Regular.woff2 │ │ ├── KaTeX_Size1-Regular.woff2 │ │ ├── KaTeX_Size2-Regular.woff2 │ │ ├── KaTeX_Size3-Regular.woff2 │ │ ├── KaTeX_Size4-Regular.woff2 │ │ ├── KaTeX_Typewriter-Regular.woff2 │ │ ├── fira-mono-v14-latin-500.woff2 │ │ ├── fira-mono-v14-latin-regular.woff2 │ │ ├── fira-sans-v17-latin-500.woff2 │ │ ├── fira-sans-v17-latin-500italic.woff2 │ │ ├── fira-sans-v17-latin-700.woff2 │ │ ├── fira-sans-v17-latin-700italic.woff2 │ │ ├── fira-sans-v17-latin-italic.woff2 │ │ ├── fira-sans-v17-latin-regular.woff2 │ │ ├── noticia-text-v15-latin-700.woff2 │ │ ├── noticia-text-v15-latin-italic.woff2 │ │ └── noticia-text-v15-latin-regular.woff2 │ ├── highlight.pack.js │ ├── katex.min.css │ ├── katex.min.js │ ├── odoc.css │ └── odoc_search.js ├── screenshots │ ├── example-animation.png │ ├── example-keyboard.png │ ├── example-misc.png │ ├── example-mouse.png │ ├── example-picture.png │ ├── example-smiley.png │ ├── example-turtle.png │ ├── example-words.png │ ├── game-asteroid.png │ ├── game-pong.png │ ├── game-snake.png │ ├── game-tetris.png │ ├── keyboard-game-native.png │ └── keyboard-game-start-native.png ├── toy-native-example │ ├── Makefile │ ├── README.md │ ├── Toy.ml │ ├── dune │ ├── dune-project │ └── toy-game.opam └── toy-web-example │ ├── Makefile │ ├── README.md │ ├── Toy.ml │ ├── dune │ ├── dune-project │ ├── static │ ├── Toy.bc.js │ └── Toy.html │ └── toy-web-game.opam ├── dune ├── dune-project ├── elm_core.opam ├── elm_playground.opam ├── elm_playground_native.opam ├── elm_playground_native.opam.template ├── elm_playground_web.opam ├── elm_system.opam ├── examples ├── Animation.ml ├── Keyboard.ml ├── Mario.ml ├── Misc.ml ├── Mouse.ml ├── Picture.ml ├── Smiley.ml ├── Turtle.ml ├── Words.ml └── dune ├── examples_js ├── Animation.html ├── Animation.ml ├── Keyboard.html ├── Keyboard.ml ├── Mario.html ├── Mario.ml ├── Mouse.html ├── Mouse.ml ├── Picture.html ├── Picture.ml ├── Smiley.html ├── Smiley.ml ├── Template.html ├── Turtle.html ├── Turtle.ml ├── Words.html ├── Words.ml ├── dune └── test.html ├── games ├── Asteroid.ml ├── Pong.ml ├── Snake.ml ├── Tetris.ml ├── dune └── template.ml ├── games_js ├── Asteroid.html ├── Asteroid.ml ├── Pong.html ├── Pong.ml ├── Snake.html ├── Snake.ml ├── Template.html ├── Tetris.html ├── Tetris.ml └── dune ├── playground ├── Playground.ml ├── Playground.mli ├── Playground_platform.mli ├── dune ├── index.mld ├── native │ ├── Playground_platform.ml │ └── dune └── web │ ├── Playground_platform.ml │ └── dune ├── semgrep.jsonnet ├── skip_list.txt ├── system ├── Cmd.ml ├── Sub.ml ├── dune └── pad.txt └── tests ├── Svg.html ├── Svg.ml ├── Test_cairo_graphics.ml ├── Test_ocamlsdl2.ml ├── Test_tsdl.ml ├── Test_vdom.html ├── Test_vdom.ml └── dune /.github/workflows/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to automatically convert the .jsonnet GHA workflows 2 | # to .yml (GHA accepts only the YAML format). 3 | 4 | LIBS = 5 | OBJS = build-and-test.yml 6 | 7 | all: $(OBJS) 8 | 9 | %.yml: %.jsonnet $(LIBS) 10 | echo "# AUTOGENERATED FROM $< DO NOT MODIFY" > $@ 11 | ./jsonnet_to_yaml.sh $< >> $@ || { rm -f $@; exit 1; } 12 | 13 | clean: 14 | rm -f $(OBJS) 15 | 16 | format: 17 | jsonnetfmt -i *.jsonnet 18 | 19 | # for pad's codemap 20 | .PHONY: gen-codemapignore 21 | gen-codemapignore: $(OBJS) 22 | rm -f .codemapignore 23 | for i in $(OBJS); do \ 24 | echo $$i >> .codemapignore; \ 25 | done 26 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.jsonnet: -------------------------------------------------------------------------------- 1 | // Build and test using setup-ocaml@v3 mostly. 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Helpers 5 | // ---------------------------------------------------------------------------- 6 | 7 | local checkout = { 8 | uses: 'actions/checkout@v3', 9 | }; 10 | 11 | // ---------------------------------------------------------------------------- 12 | // The job 13 | // ---------------------------------------------------------------------------- 14 | 15 | local job = { 16 | strategy: { 17 | matrix: { 18 | 'os': [ 19 | 'ubuntu-latest', 20 | 'macos-latest', 21 | // does not work because of tsdl compilation error on windows 22 | //'windows-latest' 23 | ], 24 | 'ocaml-compiler': [ 25 | '4.14.1', 26 | '5.2.0', 27 | ], 28 | }, 29 | //'fail-fast': false, 30 | }, 31 | 'runs-on': '${{ matrix.os }}', 32 | steps: [ 33 | checkout, 34 | { 35 | uses: 'ocaml/setup-ocaml@v3', 36 | with: { 37 | 'ocaml-compiler': '${{ matrix.ocaml-compiler }}', 38 | // I used to have "'opam-depext': false" below, but this flag 39 | // is not supported in setup@v3 and anyway we do want 40 | // opam-depext otherwise the installation would fail 41 | // because some packages like ocurl have external 42 | // dependencies (depext) and require some system packages 43 | // to be installed. opam-depext does this for us automatically 44 | // and in a portable way (for linux/mac/windows)!. 45 | }, 46 | }, 47 | { 48 | name: 'Install dependencies', 49 | run: ||| 50 | opam install --deps-only . 51 | |||, 52 | }, 53 | { 54 | name: 'Build', 55 | run: ||| 56 | eval $(opam env) 57 | make 58 | |||, 59 | }, 60 | { 61 | name: 'Test', 62 | run: ||| 63 | eval $(opam env) 64 | make test 65 | |||, 66 | }, 67 | 68 | ], 69 | }; 70 | 71 | // ---------------------------------------------------------------------------- 72 | // The workflow 73 | // ---------------------------------------------------------------------------- 74 | { 75 | name: 'build-and-test', 76 | on: { 77 | // can be run manually from the GHA dashboard 78 | workflow_dispatch: null, 79 | // on the PR 80 | pull_request: null, 81 | // and another time once the PR is merged on master 82 | push: { 83 | branches: [ 84 | 'master', 85 | ], 86 | }, 87 | // disabled the cron for now because github complains after 88 | // a few months of inactivity when there is no new commit 89 | // but the crons ran many times, so simpler to disable for now 90 | //schedule: [ 91 | // { 92 | // // every day at 12:59 93 | // cron: '59 12 * * *', 94 | // }, 95 | //], 96 | }, 97 | jobs: { 98 | job: job, 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED FROM build-and-test.jsonnet DO NOT MODIFY 2 | jobs: 3 | job: 4 | runs-on: ${{ matrix.os }} 5 | steps: 6 | - uses: actions/checkout@v3 7 | - uses: ocaml/setup-ocaml@v3 8 | with: 9 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 10 | - name: Install dependencies 11 | run: | 12 | opam install --deps-only . 13 | - name: Build 14 | run: | 15 | eval $(opam env) 16 | make 17 | - name: Test 18 | run: | 19 | eval $(opam env) 20 | make test 21 | strategy: 22 | matrix: 23 | ocaml-compiler: 24 | - 4.14.1 25 | - 5.2.0 26 | os: 27 | - ubuntu-latest 28 | - macos-latest 29 | name: build-and-test 30 | on: 31 | pull_request: null 32 | push: 33 | branches: 34 | - master 35 | workflow_dispatch: null 36 | -------------------------------------------------------------------------------- /.github/workflows/jsonnet_to_yaml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # This script requires to have: 6 | # - jsonnet (any jsonnet should work, e.g., go-jsonnet) 7 | # - yq (a.k.a go-yq) from https://github.com/mikefarah/yq 8 | # Note that many distributions come with a default yq that is not 9 | # as powerful as https://github.com/mikefarah/yq so follow 10 | # the instructions there to install yq on your machine 11 | # 12 | # The sed command is because 'on' is printed with or without quotes depending 13 | # on the version. It's a dirty hack that may break some input. 14 | 15 | jsonnet "$@" \ 16 | | yq eval -P \ 17 | | sed -e 's/^\( *\)"on":/\1on:/' \ 18 | | sed -e 's/: "yes"$/: yes/' 19 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | job: 3 | runs-on: ubuntu-20.04 4 | container: 5 | # living on the edge! 6 | image: semgrep/semgrep:pro-develop 7 | #TODO: at some point 8 | #env: 9 | # SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 10 | # ... 11 | # run: semgrep ci 12 | steps: 13 | - uses: actions/checkout@v3 14 | # TODO: not sure why we need that, the docker contain should do 15 | # similar things with ~root/.gitconfig 16 | - run: git config --global --add safe.directory /__w/ocaml-elm-playground/ocaml-elm-playground 17 | # coupling: with Makefile 'make check' target 18 | - run: semgrep scan --experimental --config semgrep.jsonnet --strict --error --debug 19 | 20 | name: semgrep 21 | on: 22 | # old was pull_request_target but has weird effect like not using 23 | # the latest change in this file 24 | pull_request: null 25 | workflow_dispatch: null 26 | push: 27 | branches: 28 | - master 29 | #schedule: 30 | # - cron: 50 15 * * * 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | .merlin 3 | 4 | todo.txt 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.1 (2024-12-24) 4 | 5 | First announce at https://discuss.ocaml.org/t/ann-first-release-of-elm-playground/15838 6 | and a better README at https://github.com/aryx/ocaml-elm-playground 7 | 8 | ## 0.2.0 (2024-12-13) 9 | 10 | First website at https://aryx.github.io/ocaml-elm-playground/ 11 | 12 | ## 0.1.0 (2024-12-8) 13 | 14 | First release on OPAM (actually 0.1.7 was the version released). 15 | 16 | ## Beta (2021-01-05) 17 | 18 | Initial native (via TSDL) and web (via vdom) "backends" 19 | and a few basic games (Pong, Asteroid, Snake, and even Tetris!) 20 | working in native and a few working in web. 21 | 22 | ## Alpha (2020-11-02) 23 | 24 | First commits and first prototype. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-present, Evan Czaplicki. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Prelude 3 | ############################################################################### 4 | 5 | ############################################################################### 6 | # Main targets 7 | ############################################################################### 8 | 9 | OPAMS=\ 10 | elm_core.opam elm_system.opam\ 11 | elm_playground.opam elm_playground_native.opam elm_playground_web.opam 12 | 13 | default: all 14 | 15 | all: $(OPAMS) 16 | dune build 17 | clean: 18 | dune clean 19 | install: 20 | dune install 21 | 22 | test: 23 | dune runtest -f 24 | 25 | # This will fail if the .opam isn't up-to-date (in git), 26 | # and dune isn't installed yet. You can always install dune 27 | # with 'opam install dune' to get started. 28 | %.opam: dune-project 29 | dune build $@ 30 | 31 | ############################################################################### 32 | # Release 33 | ############################################################################### 34 | 35 | ############################################################################### 36 | # Website building 37 | ############################################################################### 38 | 39 | doc: 40 | dune build @doc 41 | 42 | # Note that I've configured Github Pages for this project at 43 | # https://github.com/aryx/ocaml-elm-playground/settings/pages 44 | # I've selected "Deploy from Branch" "master" and "/docs" 45 | # so it assumes all the html are under docs/. 46 | # Note that if you change the settings, you need to commit in 47 | # the master branch to trigger a redeploy 48 | # TODO: automatically update games/ and examples/ 49 | # and add entries for those dirs. 50 | website: 51 | rm -rf docs 52 | make doc 53 | cp -a _build/default/_doc/_html docs 54 | make js 55 | 56 | # Preview the site at http://localhost:8000 57 | serve: 58 | python -m http.server --directory docs 8000 59 | 60 | js: 61 | dune build games_js --profile=release-js 62 | dune build examples_js --profile=release-js 63 | 64 | ############################################################################### 65 | # Developer targets 66 | ############################################################################### 67 | 68 | check: 69 | ~/zz/bin/osemgrep --experimental --config semgrep.jsonnet . 70 | 71 | # To bump-version you need to modify dune-project version then run 'make' then 72 | # commit and merge then: 73 | # git tag -a 0.1.8 74 | # git push origin 0.1.8 75 | # opam publish 76 | # and that's it! 77 | bump: 78 | echo TODO, see the comment in this file 79 | 80 | pr: 81 | git push origin `git rev-parse --abbrev-ref HEAD` 82 | hub pull-request -b master 83 | push: 84 | git push origin `git rev-parse --abbrev-ref HEAD` 85 | merge: 86 | A=`git rev-parse --abbrev-ref HEAD` && git checkout master && git pull && git branch -D $$A 87 | 88 | visual: 89 | codemap -screen_size 3 -filter pfff -efuns_client efuns_client -emacs_client /dev/null . 90 | 91 | opendoc: 92 | dune build @doc 93 | open _build/default/_doc/_html/index.html 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OCaml Elm Playground 2 | ======================= 3 | 4 | Create pictures, animations, and video games with OCaml! 5 | 6 | This is a port of the excellent Elm playground package 7 | https://github.com/evancz/elm-playground to OCaml. 8 | 9 | > This is the package I wanted when I was learning programming. Start by 10 | > putting shapes on screen and work up to making games. I hope this 11 | > package will be fun for a broad range of ages and backgrounds! 12 | > *- Evan Czaplicki* 13 | 14 | 15 | Documentation 16 | --------------------------------------------------- 17 | 18 | * [Getting started](https://github.com/aryx/ocaml-elm-playground?tab=readme-ov-file#ocaml-elm-playground) (this file) 19 | * [Tutorial](https://aryx.github.io/ocaml-elm-playground/elm_playground/) 20 | * [Basic examples](https://aryx.github.io/ocaml-elm-playground/examples/) 21 | * [Basic games](https://aryx.github.io/ocaml-elm-playground/games/) 22 | * [API reference](https://aryx.github.io/ocaml-elm-playground/elm_playground/Playground/) 23 | * [Index](https://aryx.github.io/ocaml-elm-playground) 24 | 25 | Features 26 | -------------- 27 | 28 | The OCaml `elm_playground` package allows you to easily create 29 | *pictures*, *animations*, and even *video games* in a portable way using an API that 30 | really simplifies how to view the computer and its devices (the screen, 31 | keyboard, and mouse). 32 | 33 | The goal is similar to the old [`graphics` package](https://github.com/ocaml/graphics) 34 | but goes even further in terms of simplification. 35 | 36 | The main API is defined in a single 37 | [Playground.mli](https://github.com/aryx/ocaml-elm-playground/blob/master/playground/Playground.mli) module and is implemented by two backends: 38 | - a *native* (SDL-based) backend to run your game on your desktop from a terminal 39 | - a *web* (vdom-based) backend to run your game in a browser 40 | 41 | Here is for example a simple [Snake game](https://aryx.github.io/ocaml-elm-playground/games/Snake.html) you can run from your browser (use the arrow keys to change the direction of the snake and eat the ball to grow your length). You can run the same game 42 | on your desktop *without changing a line of code*. 43 | 44 | Install 45 | -------------- 46 | 47 | To install the playground, run `opam install elm_playground` and then 48 | install one or both backends with `opam install elm_playground_native` 49 | and/or `opam install elm_playground_web`. 50 | 51 | Simple native application 52 | -------------------------- 53 | 54 | Here is a very simple application using the playground: 55 | ```ocaml 56 | open Playground 57 | 58 | (* the (x, y) position of the blue square *) 59 | type model = (float * float) 60 | 61 | let initial_state : model = (0., 0.) 62 | 63 | let view _computer (x, y) = [ 64 | square blue 40. 65 | |> move x y 66 | ] 67 | 68 | let update computer (x, y) = 69 | (x +. to_x computer.keyboard, y +. to_y computer.keyboard) 70 | 71 | let app = 72 | game view update initial_state 73 | 74 | let main = Playground_platform.run_app app 75 | ``` 76 | 77 | 78 | It is a very simple `game` defining a `model` type, a `view` function, and an `update` function to specify how the game behaves. It is using the 79 | "Model-View-Update" architecture to organize the code of a graphical 80 | interactive application 81 | (see https://guide.elm-lang.org/architecture/ for more information). 82 | 83 | To compile this application, simply do: 84 | ```bash 85 | $ cd docs/toy-native-example 86 | $ opam install --deps-only --yes . 87 | $ dune exec --root . ./Toy.exe 88 | ``` 89 | You should then see on your desktop: 90 | 91 | Toy app screenshot 93 | 94 | If you type on the arrow keys on your keyboard the blue square should move in the 95 | corresponding direction. If you type `q` it will exit the game. 96 | 97 | Note that with the Playground API the center of the screen is at `(0, 0)`. 98 | 99 | Simple web application 100 | -------------------------- 101 | 102 | To compile this same application for the web, simply do: 103 | 104 | ```bash 105 | $ cd docs/toy-web-example 106 | $ opam install --deps-only --yes . 107 | $ dune build --root . 108 | $ cp _build/default/Toy.bc.js static/ 109 | ``` 110 | You should then be able to use the web app by going to: 111 | https://aryx.github.io/ocaml-elm-playground/toy-web-example/static/Toy.html 112 | 113 | By default the generated javascript file can be big so to get a smaller one 114 | you can do instead: 115 | ```bash 116 | $ dune build --root . --profile=release 117 | $ cp _build/default/Toy.bc.js static/ 118 | ``` 119 | 120 | Next steps 121 | ------------ 122 | 123 | Read the tutorial at: 124 | https://aryx.github.io/ocaml-elm-playground/elm_playground/index.html 125 | 126 | Look at the code under [examples/](examples/) and [games/](games/). 127 | 128 | Here is a screenshot of the [Tetris](games/Tetris.ml) Playgound game running: 129 | Toy app screenshot 131 | 132 | You can see a few more screenshots [here](docs/screenshots/). 133 | 134 | Limitations 135 | ------------ 136 | 137 | The web backend is not fully finished yet. Many of the examples and games 138 | do not work fully yet on the web. You've been warned. 139 | Contributions are welcome! 140 | -------------------------------------------------------------------------------- /core/Basics.ml: -------------------------------------------------------------------------------- 1 | let log s = 2 | Printf.printf "%s" s 3 | 4 | let (/..) = (/) 5 | let (+..) = (+) 6 | let (-..) = (-) 7 | let ( *.. ) = ( * ) 8 | 9 | (* prefer float operators as default *) 10 | let (/) = (/.) 11 | let (+) = (+.) 12 | let (-) = (-.) 13 | let ( * ) = ( *. ) 14 | 15 | let (round: float -> int) = fun f -> 16 | int_of_float (floor (f +. 0.5)) 17 | 18 | let mod_by a b = 19 | b mod a 20 | 21 | let pi = Float.pi 22 | let pi2 = 8. *. atan 1. 23 | 24 | (* was called just degrees in Basics.elm *) 25 | let degrees_to_radians deg = 26 | (deg * pi) / 180. 27 | 28 | let radians_to_degrees rad = 29 | rad * 180. / pi 30 | 31 | let (turns: float -> float) = fun angle_in_turns -> 32 | 2. * pi * angle_in_turns 33 | 34 | let (clamp: 'number -> 'number -> 'number -> 'number) = fun low high number -> 35 | if number < low then 36 | low 37 | else if number > high then 38 | high 39 | else 40 | number 41 | -------------------------------------------------------------------------------- /core/Color.ml: -------------------------------------------------------------------------------- 1 | type t = 2 | | Hex of string 3 | | Rgb of int * int * int 4 | 5 | let (color_clamp : int -> int) = fun number -> 6 | Basics.clamp 0 255 number 7 | 8 | let (rgb : int -> int -> int -> t) = fun r g b -> 9 | Rgb (color_clamp r, color_clamp g, color_clamp b) 10 | 11 | 12 | let white = Hex "#FFFFFF" 13 | let black = Hex "#000000" 14 | 15 | let red = Hex "#cc0000" 16 | let green = Hex "#73d216" 17 | let blue = Hex "#3465a4" 18 | 19 | let yellow = Hex "#edd400" 20 | 21 | let brown = Hex "#c17d11" 22 | 23 | (*-------------------------------------------------------------------*) 24 | (* Light colors *) 25 | (*-------------------------------------------------------------------*) 26 | 27 | let lightYellow = Hex "#fce94f" 28 | let lightPurple = Hex "#ad7fa8" 29 | 30 | (*-------------------------------------------------------------------*) 31 | (* Dark colors *) 32 | (*-------------------------------------------------------------------*) 33 | 34 | (*-------------------------------------------------------------------*) 35 | (* Shades of grey *) 36 | (*-------------------------------------------------------------------*) 37 | 38 | let gray = Hex "#d3d7cf" 39 | 40 | let darkGray = Hex "#babdb6" 41 | -------------------------------------------------------------------------------- /core/Keyboard.ml: -------------------------------------------------------------------------------- 1 | 2 | type key = string 3 | -------------------------------------------------------------------------------- /core/Set.ml: -------------------------------------------------------------------------------- 1 | type 'a t = 'a Set_.t 2 | let empty = Set_.empty 3 | let insert = Set_.add 4 | let remove = Set_.remove 5 | -------------------------------------------------------------------------------- /core/Set_.ml: -------------------------------------------------------------------------------- 1 | (*pad: taken from set.ml from stdlib ocaml, functor sux: module Make(Ord: OrderedType) = *) 2 | (* with some addons such as from list *) 3 | 4 | (***********************************************************************) 5 | (* *) 6 | (* Objective Caml *) 7 | (* *) 8 | (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) 9 | (* *) 10 | (* Copyright 1996 Institut National de Recherche en Informatique et *) 11 | (* en Automatique. All rights reserved. This file is distributed *) 12 | (* under the terms of the GNU Library General Public License, with *) 13 | (* the special exception on linking described in file ../LICENSE. *) 14 | (* *) 15 | (***********************************************************************) 16 | 17 | (* set.ml 1.18.4.1 2004/11/03 21:19:49 doligez Exp *) 18 | 19 | (* Sets over ordered types *) 20 | 21 | (* pad: 22 | type elt = Ord.t 23 | type t = Empty | Node of t * elt * t * int 24 | and subst all Ord.compare with just compare 25 | *) 26 | type 'elt t = Empty | Node of 'elt t * 'elt * 'elt t * int 27 | 28 | (* Sets are represented by balanced binary trees (the heights of the 29 | children differ by at most 2 *) 30 | 31 | let height = function 32 | | Empty -> 0 33 | | Node (_, _, _, h) -> h 34 | 35 | (* Creates a new node with left son l, value v and right son r. 36 | We must have all elements of l < v < all elements of r. 37 | l and r must be balanced and | height l - height r | <= 2. 38 | Inline expansion of height for better speed. *) 39 | 40 | let create l v r = 41 | let hl = 42 | match l with 43 | | Empty -> 0 44 | | Node (_, _, _, h) -> h 45 | in 46 | let hr = 47 | match r with 48 | | Empty -> 0 49 | | Node (_, _, _, h) -> h 50 | in 51 | Node (l, v, r, if hl >= hr then hl + 1 else hr + 1) 52 | 53 | (* Same as create, but performs one step of rebalancing if necessary. 54 | Assumes l and r balanced and | height l - height r | <= 3. 55 | Inline expansion of create for better speed in the most frequent case 56 | where no rebalancing is required. *) 57 | 58 | let bal l v r = 59 | let hl = 60 | match l with 61 | | Empty -> 0 62 | | Node (_, _, _, h) -> h 63 | in 64 | let hr = 65 | match r with 66 | | Empty -> 0 67 | | Node (_, _, _, h) -> h 68 | in 69 | if hl > hr + 2 then 70 | match l with 71 | | Empty -> invalid_arg "Set.bal" 72 | | Node (ll, lv, lr, _) -> ( 73 | if height ll >= height lr then create ll lv (create lr v r) 74 | else 75 | match lr with 76 | | Empty -> invalid_arg "Set.bal" 77 | | Node (lrl, lrv, lrr, _) -> 78 | create (create ll lv lrl) lrv (create lrr v r)) 79 | else if hr > hl + 2 then 80 | match r with 81 | | Empty -> invalid_arg "Set.bal" 82 | | Node (rl, rv, rr, _) -> ( 83 | if height rr >= height rl then create (create l v rl) rv rr 84 | else 85 | match rl with 86 | | Empty -> invalid_arg "Set.bal" 87 | | Node (rll, rlv, rlr, _) -> 88 | create (create l v rll) rlv (create rlr rv rr)) 89 | else Node (l, v, r, if hl >= hr then hl + 1 else hr + 1) 90 | 91 | (* Insertion of one element *) 92 | 93 | let rec add x = function 94 | | Empty -> Node (Empty, x, Empty, 1) 95 | | Node (l, v, r, _) as t -> 96 | let c = compare x v in 97 | if c = 0 then t 98 | else if c < 0 then bal (add x l) v r 99 | else bal l v (add x r) 100 | 101 | (* Same as create and bal, but no assumptions are made on the 102 | relative heights of l and r. *) 103 | 104 | let rec join l v r = 105 | match (l, r) with 106 | | Empty, _ -> add v r 107 | | _, Empty -> add v l 108 | | Node (ll, lv, lr, lh), Node (rl, rv, rr, rh) -> 109 | if lh > rh + 2 then bal ll lv (join lr v r) 110 | else if rh > lh + 2 then bal (join l v rl) rv rr 111 | else create l v r 112 | 113 | (* Smallest and greatest element of a set *) 114 | 115 | let rec min_elt = function 116 | | Empty -> raise Not_found 117 | | Node (Empty, v, _r, _) -> v 118 | | Node (l, _v, _r, _) -> min_elt l 119 | 120 | let rec max_elt = function 121 | | Empty -> raise Not_found 122 | | Node (_l, v, Empty, _) -> v 123 | | Node (_l, _v, r, _) -> max_elt r 124 | 125 | (* Remove the smallest element of the given set *) 126 | 127 | let rec remove_min_elt = function 128 | | Empty -> invalid_arg "Set.remove_min_elt" 129 | | Node (Empty, _v, r, _) -> r 130 | | Node (l, v, r, _) -> bal (remove_min_elt l) v r 131 | 132 | (* Merge two trees l and r into one. 133 | All elements of l must precede the elements of r. 134 | Assume | height l - height r | <= 2. *) 135 | 136 | let merge t1 t2 = 137 | match (t1, t2) with 138 | | Empty, t -> t 139 | | t, Empty -> t 140 | | _, _ -> bal t1 (min_elt t2) (remove_min_elt t2) 141 | 142 | (* Merge two trees l and r into one. 143 | All elements of l must precede the elements of r. 144 | No assumption on the heights of l and r. *) 145 | 146 | let concat t1 t2 = 147 | match (t1, t2) with 148 | | Empty, t -> t 149 | | t, Empty -> t 150 | | _, _ -> join t1 (min_elt t2) (remove_min_elt t2) 151 | 152 | (* Splitting. split x s returns a triple (l, present, r) where 153 | - l is the set of elements of s that are < x 154 | - r is the set of elements of s that are > x 155 | - present is false if s contains no element equal to x, 156 | or true if s contains an element equal to x. *) 157 | 158 | let rec split x = function 159 | | Empty -> (Empty, false, Empty) 160 | | Node (l, v, r, _) -> 161 | let c = compare x v in 162 | if c = 0 then (l, true, r) 163 | else if c < 0 then 164 | let ll, pres, rl = split x l in 165 | (ll, pres, join rl v r) 166 | else 167 | let lr, pres, rr = split x r in 168 | (join l v lr, pres, rr) 169 | 170 | (* Implementation of the set operations *) 171 | 172 | let empty = Empty 173 | 174 | let is_empty = function 175 | | Empty -> true 176 | | _ -> false 177 | 178 | let rec mem x = function 179 | | Empty -> false 180 | | Node (l, v, r, _) -> 181 | let c = compare x v in 182 | c = 0 || mem x (if c < 0 then l else r) 183 | 184 | let singleton x = Node (Empty, x, Empty, 1) 185 | 186 | let rec remove x = function 187 | | Empty -> Empty 188 | | Node (l, v, r, _) -> 189 | let c = compare x v in 190 | if c = 0 then merge l r 191 | else if c < 0 then bal (remove x l) v r 192 | else bal l v (remove x r) 193 | 194 | let rec union s1 s2 = 195 | match (s1, s2) with 196 | | Empty, t2 -> t2 197 | | t1, Empty -> t1 198 | | Node (l1, v1, r1, h1), Node (l2, v2, r2, h2) -> 199 | if h1 >= h2 then 200 | if h2 = 1 then add v2 s1 201 | else 202 | let l2, _, r2 = split v1 s2 in 203 | join (union l1 l2) v1 (union r1 r2) 204 | else if h1 = 1 then add v1 s2 205 | else 206 | let l1, _, r1 = split v2 s1 in 207 | join (union l1 l2) v2 (union r1 r2) 208 | 209 | let rec inter s1 s2 = 210 | match (s1, s2) with 211 | | Empty, _t2 -> Empty 212 | | _t1, Empty -> Empty 213 | | Node (l1, v1, r1, _), t2 -> ( 214 | match split v1 t2 with 215 | | l2, false, r2 -> concat (inter l1 l2) (inter r1 r2) 216 | | l2, true, r2 -> join (inter l1 l2) v1 (inter r1 r2)) 217 | 218 | let rec diff s1 s2 = 219 | match (s1, s2) with 220 | | Empty, _t2 -> Empty 221 | | t1, Empty -> t1 222 | | Node (l1, v1, r1, _), t2 -> ( 223 | match split v1 t2 with 224 | | l2, false, r2 -> join (diff l1 l2) v1 (diff r1 r2) 225 | | l2, true, r2 -> concat (diff l1 l2) (diff r1 r2)) 226 | 227 | let rec compare_aux l1 l2 = 228 | match (l1, l2) with 229 | | [], [] -> 0 230 | | [], _ -> -1 231 | | _, [] -> 1 232 | | Empty :: t1, Empty :: t2 -> compare_aux t1 t2 233 | | Node (Empty, v1, r1, _) :: t1, Node (Empty, v2, r2, _) :: t2 -> 234 | let c = compare v1 v2 in 235 | if c <> 0 then c else compare_aux (r1 :: t1) (r2 :: t2) 236 | | Node (l1, v1, r1, _) :: t1, t2 -> 237 | compare_aux (l1 :: Node (Empty, v1, r1, 0) :: t1) t2 238 | | t1, Node (l2, v2, r2, _) :: t2 -> 239 | compare_aux t1 (l2 :: Node (Empty, v2, r2, 0) :: t2) 240 | 241 | let rec subset s1 s2 = 242 | match (s1, s2) with 243 | | Empty, _ -> true 244 | | _, Empty -> false 245 | | Node (l1, v1, r1, _), (Node (l2, v2, r2, _) as t2) -> 246 | let c = compare v1 v2 in 247 | if c = 0 then subset l1 l2 && subset r1 r2 248 | else if c < 0 then subset (Node (l1, v1, Empty, 0)) l2 && subset r1 t2 249 | else subset (Node (Empty, v1, r1, 0)) r2 && subset l1 t2 250 | 251 | let compare s1 s2 = compare_aux [ s1 ] [ s2 ] 252 | let equal s1 s2 = compare s1 s2 = 0 253 | 254 | let rec iter f = function 255 | | Empty -> () 256 | | Node (l, v, r, _) -> 257 | iter f l; 258 | f v; 259 | iter f r 260 | 261 | let rec fold f s accu = 262 | match s with 263 | | Empty -> accu 264 | | Node (l, v, r, _) -> fold f l (f v (fold f r accu)) 265 | 266 | let rec for_all p = function 267 | | Empty -> true 268 | | Node (l, v, r, _) -> p v && for_all p l && for_all p r 269 | 270 | let rec exists p = function 271 | | Empty -> false 272 | | Node (l, v, r, _) -> p v || exists p l || exists p r 273 | 274 | let filter p s = 275 | let rec filt accu = function 276 | | Empty -> accu 277 | | Node (l, v, r, _) -> filt (filt (if p v then add v accu else accu) l) r 278 | in 279 | filt Empty s 280 | 281 | let partition p s = 282 | let rec part ((t, f) as accu) = function 283 | | Empty -> accu 284 | | Node (l, v, r, _) -> 285 | part (part (if p v then (add v t, f) else (t, add v f)) l) r 286 | in 287 | part (Empty, Empty) s 288 | 289 | let rec cardinal = function 290 | | Empty -> 0 291 | | Node (l, _v, r, _) -> cardinal l + 1 + cardinal r 292 | 293 | let rec elements_aux accu = function 294 | | Empty -> accu 295 | | Node (l, v, r, _) -> elements_aux (v :: elements_aux accu r) l 296 | 297 | let elements s = elements_aux [] s 298 | let choose = min_elt 299 | 300 | (* pad: *) 301 | let (of_list : 'a list -> 'a t) = 302 | fun xs -> List.fold_left (fun a e -> add e a) empty xs 303 | 304 | (* martin: *) 305 | let pp pp_elt fmt set = 306 | let pp_comma fmt () = Format.fprintf fmt ",@ " in 307 | Format.fprintf fmt "{%a}" 308 | (Format.pp_print_list ~pp_sep:pp_comma pp_elt) 309 | (elements set) 310 | -------------------------------------------------------------------------------- /core/Set_.mli: -------------------------------------------------------------------------------- 1 | (*pad: taken from set.ml from stdlib ocaml, functor sux: module Make(Ord: OrderedType) = *) 2 | (* with some addons such as from list *) 3 | (***********************************************************************) 4 | (* *) 5 | (* Objective Caml *) 6 | (* *) 7 | (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) 8 | (* *) 9 | (* Copyright 1996 Institut National de Recherche en Informatique et *) 10 | (* en Automatique. All rights reserved. This file is distributed *) 11 | (* under the terms of the GNU Library General Public License, with *) 12 | (* the special exception on linking described in file ../LICENSE. *) 13 | (* *) 14 | (***********************************************************************) 15 | 16 | (* set.mli 1.32 2004/04/23 10:01:54 xleroy Exp $ *) 17 | 18 | (** Sets over ordered types. 19 | 20 | This module implements the set data structure, given a total ordering 21 | function over the set elements. All operations over sets 22 | are purely applicative (no side-effects). 23 | The implementation uses balanced binary trees, and is therefore 24 | reasonably efficient: insertion and membership take time 25 | logarithmic in the size of the set, for instance. 26 | *) 27 | 28 | (* pad: 29 | module type OrderedType = 30 | sig 31 | type t 32 | (** The type of the set elements. *) 33 | val compare : t -> t -> int 34 | (** A total ordering function over the set elements. 35 | This is a two-argument function [f] such that 36 | [f e1 e2] is zero if the elements [e1] and [e2] are equal, 37 | [f e1 e2] is strictly negative if [e1] is smaller than [e2], 38 | and [f e1 e2] is strictly positive if [e1] is greater than [e2]. 39 | Example: a suitable ordering function is the generic structural 40 | comparison function {!Pervasives.compare}. *) 41 | end 42 | (** Input signature of the functor {!Set.Make}. *) 43 | *) 44 | (* 45 | module type S = 46 | sig 47 | *) 48 | (* type elt *) 49 | (** The type of the set elements. *) 50 | 51 | type 'elt t 52 | (** The type of sets. *) 53 | 54 | val empty : 'elt t 55 | (** The empty set. *) 56 | 57 | val is_empty : 'elt t -> bool 58 | (** Test whether a set is empty or not. *) 59 | 60 | val mem : 'elt -> 'elt t -> bool 61 | (** [mem x s] tests whether [x] belongs to the set [s]. *) 62 | 63 | val add : 'elt -> 'elt t -> 'elt t 64 | (** [add x s] returns a set containing all elements of [s], 65 | plus [x]. If [x] was already in [s], [s] is returned unchanged. *) 66 | 67 | val singleton : 'elt -> 'elt t 68 | (** [singleton x] returns the one-element set containing only [x]. *) 69 | 70 | val remove : 'elt -> 'elt t -> 'elt t 71 | (** [remove x s] returns a set containing all elements of [s], 72 | except [x]. If [x] was not in [s], [s] is returned unchanged. *) 73 | 74 | val union : 'elt t -> 'elt t -> 'elt t 75 | (** Set union. *) 76 | 77 | val inter : 'elt t -> 'elt t -> 'elt t 78 | (** Set intersection. *) 79 | 80 | val diff : 'elt t -> 'elt t -> 'elt t 81 | (** Set difference. *) 82 | 83 | val compare : 'elt t -> 'elt t -> int 84 | (** Total ordering between sets. Can be used as the ordering function 85 | for doing sets of sets. *) 86 | 87 | val equal : 'elt t -> 'elt t -> bool 88 | (** [equal s1 s2] tests whether the sets [s1] and [s2] are 89 | equal, that is, contain equal elements. *) 90 | 91 | val subset : 'elt t -> 'elt t -> bool 92 | (** [subset s1 s2] tests whether the set [s1] is a subset of 93 | the set [s2]. *) 94 | 95 | val iter : ('elt -> unit) -> 'elt t -> unit 96 | (** [iter f s] applies [f] in turn to all elements of [s]. 97 | The elements of [s] are presented to [f] in increasing order 98 | with respect to the ordering over the type of the elements. *) 99 | 100 | val fold : ('elt -> 'a -> 'a) -> 'elt t -> 'a -> 'a 101 | (** [fold f s a] computes [(f xN ... (f x2 (f x1 a))...)], 102 | where [x1 ... xN] are the elements of [s], in increasing order. *) 103 | 104 | val for_all : ('elt -> bool) -> 'elt t -> bool 105 | (** [for_all p s] checks if all elements of the set 106 | satisfy the predicate [p]. *) 107 | 108 | val exists : ('elt -> bool) -> 'elt t -> bool 109 | (** [exists p s] checks if at least one element of 110 | the set satisfies the predicate [p]. *) 111 | 112 | val filter : ('elt -> bool) -> 'elt t -> 'elt t 113 | (** [filter p s] returns the set of all elements in [s] 114 | that satisfy predicate [p]. *) 115 | 116 | val partition : ('elt -> bool) -> 'elt t -> 'elt t * 'elt t 117 | (** [partition p s] returns a pair of sets [(s1, s2)], where 118 | [s1] is the set of all the elements of [s] that satisfy the 119 | predicate [p], and [s2] is the set of all the elements of 120 | [s] that do not satisfy [p]. *) 121 | 122 | val cardinal : 'elt t -> int 123 | (** Return the number of elements of a set. *) 124 | 125 | val elements : 'elt t -> 'elt list 126 | (** Return the list of all elements of the given set. 127 | The returned list is sorted in increasing order with respect 128 | to the ordering [Ord.compare], where [Ord] is the argument 129 | given to {!Set.Make}. *) 130 | 131 | val min_elt : 'elt t -> 'elt 132 | (** Return the smallest element of the given set 133 | (with respect to the [Ord.compare] ordering), or raise 134 | [Not_found] if the set is empty. *) 135 | 136 | val max_elt : 'elt t -> 'elt 137 | (** Same as {!Set.S.min_elt}, but returns the largest element of the 138 | given set. *) 139 | 140 | val choose : 'elt t -> 'elt 141 | (** Return one element of the given set, or raise [Not_found] if 142 | the set is empty. Which element is chosen is unspecified, 143 | but equal elements will be chosen for equal sets. *) 144 | 145 | val split : 'elt -> 'elt t -> 'elt t * bool * 'elt t 146 | (** [split x s] returns a triple [(l, present, r)], where 147 | [l] is the set of elements of [s] that are 148 | strictly less than [x]; 149 | [r] is the set of elements of [s] that are 150 | strictly greater than [x]; 151 | [present] is [false] if [s] contains no element equal to [x], 152 | or [true] if [s] contains an element equal to [x]. *) 153 | 154 | val of_list : 'elt list -> 'elt t 155 | (* 156 | end 157 | (** Output signature of the functor {!Set.Make}. *) 158 | 159 | module Make (Ord : OrderedType) : S with type elt = Ord.t 160 | (** Functor building an implementation of the set structure 161 | given a totally ordered type. *) 162 | *) 163 | 164 | (* Pretty-print a set as e.g. {1, 2, 3} *) 165 | val pp : 166 | (Format.formatter -> 'elt -> unit) -> Format.formatter -> 'elt t -> unit 167 | -------------------------------------------------------------------------------- /core/Time.ml: -------------------------------------------------------------------------------- 1 | type posix = float 2 | let millis_to_posix n = 3 | float_of_int n 4 | let posix_to_millis n = 5 | int_of_float ( n *. 1000.) 6 | -------------------------------------------------------------------------------- /core/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name elm_core) 3 | (wrapped false) 4 | (libraries 5 | ; commons; was for Set_.ml but now included here 6 | ) 7 | ; (preprocess (pps profiling.ppx)) 8 | ) 9 | -------------------------------------------------------------------------------- /docs/elm_core/Basics/index.html: -------------------------------------------------------------------------------- 1 | 2 | Basics (elm_core.Basics)

Module Basics

val log : string -> unit
val (/..) : int -> int -> int
val (+..) : int -> int -> int
val (-..) : int -> int -> int
val (*..) : int -> int -> int
val (/) : float -> float -> float
val (+) : float -> float -> float
val (-) : float -> float -> float
val (*) : float -> float -> float
val round : float -> int
val mod_by : int -> int -> int
val pi : float
val pi2 : float
val degrees_to_radians : float -> float
val radians_to_degrees : float -> float
val turns : float -> float
val clamp : 'number -> 'number -> 'number -> 'number
3 | -------------------------------------------------------------------------------- /docs/elm_core/Color/index.html: -------------------------------------------------------------------------------- 1 | 2 | Color (elm_core.Color)

Module Color

type t =
  1. | Hex of string
  2. | Rgb of int * int * int
val color_clamp : int -> int
val rgb : int -> int -> int -> t
val white : t
val black : t
val red : t
val green : t
val blue : t
val yellow : t
val brown : t
val lightYellow : t
val lightPurple : t
val gray : t
val darkGray : t
3 | -------------------------------------------------------------------------------- /docs/elm_core/Keyboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | Keyboard (elm_core.Keyboard)

Module Keyboard

type key = string
3 | -------------------------------------------------------------------------------- /docs/elm_core/Set/index.html: -------------------------------------------------------------------------------- 1 | 2 | Set (elm_core.Set)

Module Set

type 'a t = 'a Set_.t
val empty : 'a Set_.t
val insert : 'a -> 'a Set_.t -> 'a Set_.t
val remove : 'a -> 'a Set_.t -> 'a Set_.t
3 | -------------------------------------------------------------------------------- /docs/elm_core/Time/index.html: -------------------------------------------------------------------------------- 1 | 2 | Time (elm_core.Time)

Module Time

type posix = float
val millis_to_posix : int -> float
val posix_to_millis : float -> int
3 | -------------------------------------------------------------------------------- /docs/elm_core/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (elm_core.index)

elm_core index

Library elm_core

This library exposes the following toplevel modules:

3 | -------------------------------------------------------------------------------- /docs/elm_playground/Playground_platform/index.html: -------------------------------------------------------------------------------- 1 | 2 | Playground_platform (elm_playground.Playground_platform)

Module Playground_platform

val run_app : ('a, 'b) Playground.app -> unit
3 | -------------------------------------------------------------------------------- /docs/elm_playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (elm_playground.index)

elm_playground

OCaml Elm Playground

Create pictures, animations, and games with OCaml!

This is a port of the excellent Elm playground package to OCaml.

This is the package I wanted when I was learning programming. Start by putting shapes on screen and work up to making games. I hope this package will be fun for a broad range of ages and backgrounds!

Pictures

A picture is a list of shapes. For example, this picture combines a brown rectangle and a green circle to make a tree

open Playground
 3 | 
 4 | let app =
 5 |   picture [
 6 |     rectangle brown 40. 200.;
 7 |     circle green 100.
 8 |       |> move_up 100.;
 9 |   ]
10 | 
11 | let main = Playground_platform.run_app app

Play around to get familiar with all the different shapes and transformations in the library.

Animations

An animation is a list of shapes that changes over time. For example, here is a spinning triangle:

open Playground
12 | 
13 | let view time = [ 
14 |    triangle orange 50.
15 |       |> rotate (spin 8. time);
16 |   ]
17 | 
18 | let app = animation view
19 | 
20 | let main = Playground_platform.run_app app

It will do a full spin every 8 seconds.

Maybe try making a car with spinning octogons as wheels? Try using Playground.wave to move things back-and-forth? Try using Playground.zigzag to fade things in-and-out?

Games

A game lets you use input from the mouse and keyboard to change your picture. For example, here is a square that moves around based on the arrow keys:

open Playground
21 | 
22 | let view _computer (x, y) = [ 
23 |   square blue 40.
24 |    |> move x y
25 |  ]
26 | 
27 | let update computer (x, y) =
28 |   (x +. to_x computer.keyboard, y +. to_y computer.keyboard)
29 | 
30 | let app = 
31 |   game view update (0., 0.)
32 | 
33 | let main = Playground_platform.run_app app

Every game has three important parts:

  1. memory - Store information. Our example stores (x,y) coordinates.
  2. update - Update the memory based on mouse movements, key presses, etc. Our example moves the (x,y) coordinate around based on the arrow keys.
  3. view - Turn the memory into a picture. Our example just shows one blue square at the (x,y) coordinate we have been tracking in memory.

When you start making fancier games, you will store fancier things in memory. There is a lot of room to develop your programming skills here: Making lists, using records, creating custom types, etc.

I started off trying to make Pong, then worked on games like Breakout and Space Invaders as I learned more and more. It was really fun, and I hope it will be for you as well!

Index of modules

34 | -------------------------------------------------------------------------------- /docs/elm_playground_native/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (elm_playground_native.index)

elm_playground_native index

3 | -------------------------------------------------------------------------------- /docs/elm_playground_web/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (elm_playground_web.index)

elm_playground_web index

3 | -------------------------------------------------------------------------------- /docs/elm_system/Cmd/index.html: -------------------------------------------------------------------------------- 1 | 2 | Cmd (elm_system.Cmd)

Module Cmd

type 'msg t =
  1. | None
  2. | Msg of 'msg
val none : 'a t
3 | -------------------------------------------------------------------------------- /docs/elm_system/Sub/index.html: -------------------------------------------------------------------------------- 1 | 2 | Sub (elm_system.Sub)

Module Sub

type 'msg onesub =
  1. | SubTick of Time.posix -> 'msg
  2. | SubMouseMove of (float * float) -> 'msg
  3. | SubMouseDown of unit -> 'msg
  4. | SubMouseUp of unit -> 'msg
  5. | SubKeyDown of Keyboard.key -> 'msg
  6. | SubKeyUp of Keyboard.key -> 'msg
type 'msg t = 'msg onesub list
val none : 'a list
val batch : 'a list list -> 'a list
val on_animation_frame : (Time.posix -> 'msg) -> 'msg t
val on_mouse_move : ((float * float) -> 'msg) -> 'msg t
val on_mouse_down : (unit -> 'msg) -> 'msg t
val on_mouse_up : (unit -> 'msg) -> 'msg t
val on_key_down : (Keyboard.key -> 'msg) -> 'msg t
val on_key_up : (Keyboard.key -> 'msg) -> 'msg t
type event =
  1. | ETick of float
  2. | EMouseMove of int * int
  3. | EMouseButton of bool
  4. | EKeyChanged of bool * Keyboard.key
val find_map_opt : ('a -> 'b option) -> 'a list -> 'b option
val event_to_msgopt : event -> 'a onesub list -> 'a option
3 | -------------------------------------------------------------------------------- /docs/elm_system/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (elm_system.index)

elm_system index

Library elm_system

This library exposes the following toplevel modules:

3 | -------------------------------------------------------------------------------- /docs/examples/Animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Keyboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Mario.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Mouse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Smiley.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Turtle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/Words.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Examples

13 |
    14 |
  1. Picture: src app
  2. 15 |
  3. Smiley: src app
  4. 16 |
  5. Animation: src app
  6. 17 |
  7. Keyboard: src app
  8. 18 |
  9. Turtle: src app
  10. 19 |
  11. Mario: src app
  12. 20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/games/Asteroid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/games/Pong.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/games/Snake.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/games/Template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/games/Tetris.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/games/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Games

13 |
    14 |
  1. Snake: src app
  2. 15 |
16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

README

13 | 14 |

OCaml package documentation

15 |

The most important module below is elm_playground. 16 |

    17 |
  1. elm_core 0.1.7
  2. 18 |
  3. elm_playground 0.1.7
  4. 19 |
  5. elm_playground_native 0.1.7
  6. 20 |
  7. elm_playground_web 0.1.7
  8. 21 |
  9. elm_system 0.1.7
  10. 22 |

    Examples

    23 |

    See examples for very basic examples. 24 |

    See toy for the toy example used in the README. 25 |

    See also games for more complex examples. 26 |

27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Main-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Main-BoldItalic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-mono-v14-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-mono-v14-latin-500.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-mono-v14-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-mono-v14-latin-regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-sans-v17-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-sans-v17-latin-500.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-sans-v17-latin-500italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-sans-v17-latin-500italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-sans-v17-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-sans-v17-latin-700.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-sans-v17-latin-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-sans-v17-latin-700italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-sans-v17-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-sans-v17-latin-italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/fira-sans-v17-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/fira-sans-v17-latin-regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/noticia-text-v15-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/noticia-text-v15-latin-700.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/noticia-text-v15-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/noticia-text-v15-latin-italic.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/fonts/noticia-text-v15-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/odoc.support/fonts/noticia-text-v15-latin-regular.woff2 -------------------------------------------------------------------------------- /docs/odoc.support/odoc_search.js: -------------------------------------------------------------------------------- 1 | /* The browsers interpretation of the CORS origin policy prevents to run 2 | webworkers from javascript files fetched from the file:// protocol. This hack 3 | is to workaround this restriction. */ 4 | function createWebWorker() { 5 | var searchs = search_urls.map((search_url) => { 6 | let parts = document.location.href.split("/"); 7 | parts[parts.length - 1] = search_url; 8 | return '"' + parts.join("/") + '"'; 9 | }); 10 | blobContents = ["importScripts(" + searchs.join(",") + ");"]; 11 | var blob = new Blob(blobContents, { type: "application/javascript" }); 12 | var blobUrl = URL.createObjectURL(blob); 13 | 14 | var worker = new Worker(blobUrl); 15 | URL.revokeObjectURL(blobUrl); 16 | 17 | return worker; 18 | } 19 | 20 | var worker; 21 | var waiting = 0; 22 | 23 | function wait() { 24 | waiting = waiting + 1; 25 | document.querySelector(".search-snake").classList.add("search-busy"); 26 | } 27 | 28 | function stop_waiting() { 29 | if (waiting > 0) waiting = waiting - 1; 30 | else waiting = 0; 31 | if (waiting == 0) { 32 | document.querySelector(".search-snake").classList.remove("search-busy"); 33 | } 34 | } 35 | 36 | document.querySelector(".search-bar").addEventListener("focus", (ev) => { 37 | if (typeof worker == "undefined") { 38 | worker = createWebWorker(); 39 | worker.onmessage = (e) => { 40 | stop_waiting(); 41 | let results = e.data; 42 | let search_results = document.querySelector(".search-result"); 43 | search_results.innerHTML = ""; 44 | let f = (entry) => { 45 | let search_result = document.createElement("a"); 46 | search_result.classList.add("search-entry"); 47 | search_result.href = base_url + entry.url; 48 | search_result.innerHTML = entry.html; 49 | search_results.appendChild(search_result); 50 | }; 51 | results.forEach(f); 52 | let search_request = document.querySelector(".search-bar").value; 53 | if (results.length == 0 && search_request != "") { 54 | let no_result = document.createElement("div"); 55 | no_result.classList.add("search-no-result"); 56 | no_result.innerText = "No result..."; 57 | search_results.appendChild(no_result); 58 | } 59 | }; 60 | } 61 | }); 62 | 63 | document.querySelector(".search-bar").addEventListener("input", (ev) => { 64 | wait(); 65 | worker.postMessage(ev.target.value); 66 | }); 67 | -------------------------------------------------------------------------------- /docs/screenshots/example-animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-animation.png -------------------------------------------------------------------------------- /docs/screenshots/example-keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-keyboard.png -------------------------------------------------------------------------------- /docs/screenshots/example-misc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-misc.png -------------------------------------------------------------------------------- /docs/screenshots/example-mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-mouse.png -------------------------------------------------------------------------------- /docs/screenshots/example-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-picture.png -------------------------------------------------------------------------------- /docs/screenshots/example-smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-smiley.png -------------------------------------------------------------------------------- /docs/screenshots/example-turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-turtle.png -------------------------------------------------------------------------------- /docs/screenshots/example-words.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/example-words.png -------------------------------------------------------------------------------- /docs/screenshots/game-asteroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/game-asteroid.png -------------------------------------------------------------------------------- /docs/screenshots/game-pong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/game-pong.png -------------------------------------------------------------------------------- /docs/screenshots/game-snake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/game-snake.png -------------------------------------------------------------------------------- /docs/screenshots/game-tetris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/game-tetris.png -------------------------------------------------------------------------------- /docs/screenshots/keyboard-game-native.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/keyboard-game-native.png -------------------------------------------------------------------------------- /docs/screenshots/keyboard-game-start-native.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/screenshots/keyboard-game-start-native.png -------------------------------------------------------------------------------- /docs/toy-native-example/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | dune build --root . 3 | clean: 4 | dune clean --root . 5 | -------------------------------------------------------------------------------- /docs/toy-native-example/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/toy-native-example/README.md -------------------------------------------------------------------------------- /docs/toy-native-example/Toy.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | (* the (x, y) position of the blue square *) 4 | type model = (float * float) 5 | 6 | let initial_state : model = (0., 0.) 7 | 8 | let view _computer (x, y) = [ 9 | square blue 40. 10 | |> move x y 11 | ] 12 | 13 | let update computer (x, y) = 14 | (x +. to_x computer.keyboard, y +. to_y computer.keyboard) 15 | 16 | let app = 17 | game view update initial_state 18 | 19 | let _main = Playground_platform.run_app app 20 | -------------------------------------------------------------------------------- /docs/toy-native-example/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name Toy) 3 | (libraries 4 | elm_playground 5 | elm_playground_native 6 | )) 7 | -------------------------------------------------------------------------------- /docs/toy-native-example/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | -------------------------------------------------------------------------------- /docs/toy-native-example/toy-game.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | depends: [ 4 | "ocaml" {>= "4.14.0"} 5 | "dune" {>= "2.0.0"} 6 | "elm_playground" 7 | "elm_playground_native" 8 | ] 9 | 10 | synopsis: "One of the OCaml Elm Playground examples" 11 | homepage: "https://github.com/aryx/ocaml-elm-playground" 12 | bug-reports: "https://github.com/aryx/ocaml-elm-playground/issues" 13 | author: "Yoann Padioleau " 14 | license: "MIT" 15 | maintainer: "Yoann Padioleau " 16 | -------------------------------------------------------------------------------- /docs/toy-web-example/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | dune build --root . 3 | cp _build/default/Toy.bc.js static 4 | clean: 5 | dune clean --root . 6 | 7 | release: 8 | dune build --root . --profile=release-js 9 | cp _build/default/Toy.bc.js static 10 | -------------------------------------------------------------------------------- /docs/toy-web-example/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryx/ocaml-elm-playground/1049f8ae6e73a418dcfc72c36e7417a1ebf7d67f/docs/toy-web-example/README.md -------------------------------------------------------------------------------- /docs/toy-web-example/Toy.ml: -------------------------------------------------------------------------------- 1 | ../toy-native-example/Toy.ml -------------------------------------------------------------------------------- /docs/toy-web-example/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name Toy) 3 | (libraries 4 | elm_playground 5 | elm_playground_web 6 | ) 7 | (modes js) 8 | (link_flags -no-check-prims) 9 | ) 10 | 11 | (env 12 | (dev 13 | ; (flags (:standard -w -6-32-37-69)) 14 | ; with dev jsoo Toy.bc.js is =~3MB 15 | (js_of_ocaml 16 | (flags 17 | ( 18 | :standard ; pretty and sourcemap 19 | --no-inline 20 | --debug-info 21 | --disable staticeval 22 | --disable share 23 | ; js_of_ocaml has a bug w/ shortvars after 5.5.2 so let's disable it 24 | --disable shortvar 25 | ; gives us better error messages 26 | --enable with-js-error)))) 27 | (release 28 | (flags (:standard))) 29 | ; with release-js Toy.bc.js is =~120KB 30 | (release-js 31 | (js_of_ocaml 32 | (flags 33 | ( 34 | --disable shortvar 35 | --enable with-js-error)))) 36 | ) 37 | -------------------------------------------------------------------------------- /docs/toy-web-example/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | -------------------------------------------------------------------------------- /docs/toy-web-example/static/Toy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/toy-web-example/toy-web-game.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | depends: [ 4 | "ocaml" {>= "4.14.0"} 5 | "dune" {>= "2.0.0"} 6 | "elm_playground" 7 | "elm_playground_web" 8 | ] 9 | 10 | synopsis: "One of the OCaml Elm Playground examples" 11 | homepage: "https://github.com/aryx/ocaml-elm-playground" 12 | bug-reports: "https://github.com/aryx/ocaml-elm-playground/issues" 13 | author: "Yoann Padioleau " 14 | license: "MIT" 15 | maintainer: "Yoann Padioleau " 16 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (env 2 | (dev 3 | ;TODO FIX! remove all those -xxx 4 | (flags (:standard -w -6-32-37-69)) 5 | ; with dev jsoo Animation.bc.js is =~3MB 6 | (js_of_ocaml 7 | ; copy from semgrep-pro/dune file 8 | (flags 9 | ( 10 | :standard ; pretty and sourcemap 11 | --no-inline 12 | --debug-info 13 | --disable staticeval 14 | --disable share 15 | ; js_of_ocaml has a bug w/ shortvars after 5.5.2 so let's disable it 16 | --disable shortvar 17 | ; gives us better error messages 18 | --enable with-js-error)))) 19 | (release 20 | (flags (:standard))) 21 | ; with release-js Animation.bc.js is =~120KB 22 | ; if you give a fake profile like dune build example_js --profile=foo 23 | ; you can get even lower with 90KB but maybe the options below are useful 24 | ; TODO: use --opt? in semgrep-pro we don't because it says it can break things 25 | (release-js 26 | (js_of_ocaml 27 | (flags 28 | ( 29 | --disable shortvar 30 | --enable with-js-error)))) 31 | 32 | ) 33 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | 3 | (name elm_playground) 4 | 5 | (generate_opam_files true) 6 | 7 | (license "LGPL-2.1-only") 8 | 9 | (authors 10 | "Yoann Padioleau = "4.07.0")) 39 | ) 40 | ) 41 | 42 | (package (name elm_system) 43 | (synopsis "Imitate system Elm functions for ease of porting") 44 | (description "") 45 | (depends 46 | elm_core 47 | ) 48 | ) 49 | 50 | (package (name elm_playground) 51 | (synopsis "Interface of the Playground") 52 | (description "\ 53 | This is just the interface of the playground. You'll need an 54 | actual platform library to compile your application 55 | (elm_playground_native or elm_playground_web) 56 | ") 57 | (depends 58 | elm_core 59 | elm_system 60 | ) 61 | ) 62 | (package (name elm_playground_native) 63 | (synopsis "Native TDSL-based playground platform") 64 | (description "") 65 | (depends 66 | ; tsdl fails in 5.2.0 because of some missing unix 67 | ; the lib fails in 4.07 because of some syntax error about an operator 68 | (ocaml (and (< "5.2.0") (>= "4.08.0"))) 69 | logs 70 | (cairo2 (>= "0.6.4")) 71 | tsdl 72 | imagelib 73 | ocurl 74 | elm_playground 75 | ) 76 | ) 77 | (package (name elm_playground_web) 78 | (synopsis "Web vdom-based playground platform") 79 | (description "") 80 | (depends 81 | ; gen_js_api fails to build in 4.10.0 82 | (ocaml (>= "4.11.0")) 83 | vdom 84 | elm_playground 85 | ) 86 | ) 87 | -------------------------------------------------------------------------------- /elm_core.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Imitate core Elm functions for ease of porting" 5 | description: """ 6 | Nope 7 | """ 8 | maintainer: ["Yoann Padioleau = "3.0"} 15 | "ocaml" {>= "4.07.0"} 16 | "odoc" {with-doc} 17 | ] 18 | build: [ 19 | ["dune" "subst"] {dev} 20 | [ 21 | "dune" 22 | "build" 23 | "-p" 24 | name 25 | "-j" 26 | jobs 27 | "@install" 28 | "@runtest" {with-test} 29 | "@doc" {with-doc} 30 | ] 31 | ] 32 | dev-repo: "git+https://github.com/aryx/ocaml-elm-playground.git" 33 | -------------------------------------------------------------------------------- /elm_playground.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Interface of the Playground" 5 | description: """ 6 | This is just the interface of the playground. You'll need an 7 | actual platform library to compile your application 8 | (elm_playground_native or elm_playground_web) 9 | """ 10 | maintainer: ["Yoann Padioleau = "3.0"} 17 | "elm_core" 18 | "elm_system" 19 | "odoc" {with-doc} 20 | ] 21 | build: [ 22 | ["dune" "subst"] {dev} 23 | [ 24 | "dune" 25 | "build" 26 | "-p" 27 | name 28 | "-j" 29 | jobs 30 | "@install" 31 | "@runtest" {with-test} 32 | "@doc" {with-doc} 33 | ] 34 | ] 35 | dev-repo: "git+https://github.com/aryx/ocaml-elm-playground.git" 36 | -------------------------------------------------------------------------------- /elm_playground_native.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Native TDSL-based playground platform" 5 | description: "" 6 | maintainer: ["Yoann Padioleau = "3.0"} 13 | "ocaml" {< "5.2.0" & >= "4.08.0"} 14 | "logs" 15 | "cairo2" {>= "0.6.4"} 16 | "tsdl" 17 | "imagelib" 18 | "ocurl" 19 | "elm_playground" 20 | "odoc" {with-doc} 21 | ] 22 | build: [ 23 | ["dune" "subst"] {dev} 24 | [ 25 | "dune" 26 | "build" 27 | "-p" 28 | name 29 | "-j" 30 | jobs 31 | "@install" 32 | "@runtest" {with-test} 33 | "@doc" {with-doc} 34 | ] 35 | ] 36 | dev-repo: "git+https://github.com/aryx/ocaml-elm-playground.git" 37 | available: [ os-family != "windows" ] 38 | -------------------------------------------------------------------------------- /elm_playground_native.opam.template: -------------------------------------------------------------------------------- 1 | available: [ os-family != "windows" ] 2 | -------------------------------------------------------------------------------- /elm_playground_web.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Web vdom-based playground platform" 5 | description: "" 6 | maintainer: ["Yoann Padioleau = "3.0"} 13 | "ocaml" {>= "4.11.0"} 14 | "vdom" 15 | "elm_playground" 16 | "odoc" {with-doc} 17 | ] 18 | build: [ 19 | ["dune" "subst"] {dev} 20 | [ 21 | "dune" 22 | "build" 23 | "-p" 24 | name 25 | "-j" 26 | jobs 27 | "@install" 28 | "@runtest" {with-test} 29 | "@doc" {with-doc} 30 | ] 31 | ] 32 | dev-repo: "git+https://github.com/aryx/ocaml-elm-playground.git" 33 | -------------------------------------------------------------------------------- /elm_system.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Imitate system Elm functions for ease of porting" 5 | description: "" 6 | maintainer: ["Yoann Padioleau = "3.0"} 13 | "elm_core" 14 | "odoc" {with-doc} 15 | ] 16 | build: [ 17 | ["dune" "subst"] {dev} 18 | [ 19 | "dune" 20 | "build" 21 | "-p" 22 | name 23 | "-j" 24 | jobs 25 | "@install" 26 | "@runtest" {with-test} 27 | "@doc" {with-doc} 28 | ] 29 | ] 30 | dev-repo: "git+https://github.com/aryx/ocaml-elm-playground.git" 31 | -------------------------------------------------------------------------------- /examples/Animation.ml: -------------------------------------------------------------------------------- 1 | (* from https://elm-lang.org/examples/animation *) 2 | open Playground 3 | 4 | let view time = [ 5 | octagon darkGray 36. 6 | |> move_left 100. 7 | |> rotate (spin 3. time); 8 | octagon darkGray 36. 9 | |> move_right 100. 10 | |> rotate (spin 3. time); 11 | rectangle red 300. 80. 12 | |> move_up (wave 50. 54. 2. time) 13 | |> rotate (zigzag (-. 2.) 2. 8. time); 14 | ] 15 | 16 | let app = 17 | animation view 18 | 19 | let main = Playground_platform.run_app app 20 | -------------------------------------------------------------------------------- /examples/Keyboard.ml: -------------------------------------------------------------------------------- 1 | (* from https://elm-lang.org/examples/mouse *) 2 | open Playground 3 | 4 | let view _computer (x, y) = [ 5 | square blue 40. 6 | |> move x y 7 | ] 8 | 9 | let update computer (x, y) = 10 | (x +. to_x computer.keyboard, y +. to_y computer.keyboard) 11 | 12 | let app = 13 | game view update (0., 0.) 14 | 15 | let main = Playground_platform.run_app app 16 | -------------------------------------------------------------------------------- /examples/Mario.ml: -------------------------------------------------------------------------------- 1 | (* from https://elm-lang.org/examples/mario *) 2 | open Playground 3 | open Basics (* float arithmetics *) 4 | 5 | type model = { 6 | x: number; 7 | y: number; 8 | vx: number; 9 | vy: number; 10 | dir: string 11 | } 12 | 13 | let initial_model = 14 | { x = 0.; 15 | y = 0.; 16 | vx = 0.; 17 | vy = 0.; 18 | dir = "right" 19 | } 20 | 21 | let to_gif mario = 22 | if mario.y > 0. then 23 | "https://elm-lang.org/images/mario/jump/" ^ mario.dir ^ ".gif" 24 | else if mario.vx <> 0. then 25 | "https://elm-lang.org/images/mario/walk/" ^ mario.dir ^ ".gif" 26 | else 27 | "https://elm-lang.org/images/mario/stand/" ^ mario.dir ^ ".gif" 28 | 29 | let view computer mario = 30 | let w = computer.screen.width in 31 | let h = computer.screen.height in 32 | let b = computer.screen.bottom in 33 | 34 | [ rectangle (rgb 174 238 238) w h; 35 | rectangle (rgb 74 163 41) w 100. 36 | |> move_y b; 37 | image 70. 70. (to_gif mario) 38 | 39 | |> move mario.x (b + 76. + mario.y) 40 | ] 41 | 42 | 43 | let update computer mario = 44 | let dt = 1.666 in 45 | let vx = to_x computer.keyboard in 46 | let vy = 47 | if mario.y = 0. then 48 | if computer.keyboard.kup then 5. else 0. 49 | else 50 | mario.vy - dt / 8. 51 | in 52 | let x = mario.x + dt * vx in 53 | let y = mario.y + dt * vy in 54 | { x; 55 | y = max 0. y; 56 | vx; 57 | vy; 58 | dir = if vx = 0. then mario.dir else if vx < 0. then "left" else "right" 59 | } 60 | 61 | let app = game view update initial_model 62 | 63 | let main = Playground_platform.run_app app 64 | 65 | -------------------------------------------------------------------------------- /examples/Misc.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | let star = 4 | group 5 | [ triangle yellow 20.; 6 | triangle yellow 20. 7 | |> rotate 180.; 8 | ] 9 | 10 | let app = 11 | picture [ 12 | polygon black [ (-10., -20.); (0., 100.); (10., -20.)]; 13 | rectangle green 10. 10. |> scale 2. |> move 20. 20. |> rotate 45.; 14 | 15 | star |> move 100. 100. |> rotate 5.; 16 | star |> move (-120.) 40. |> rotate 20.; 17 | star |> move 80. (-150.) |> rotate 32.; 18 | star |> move (-90.) (-30.) |> rotate (-16.); 19 | ] 20 | 21 | let main = Playground_platform.run_app app 22 | -------------------------------------------------------------------------------- /examples/Mouse.ml: -------------------------------------------------------------------------------- 1 | (* from https://elm-lang.org/examples/mouse *) 2 | open Playground 3 | 4 | let view computer _memory = [ 5 | rectangle yellow computer.screen.width computer.screen.height; 6 | circle lightPurple 30. 7 | |> move_x computer.mouse.mx 8 | |> move_y computer.mouse.my 9 | |> fade (if computer.mouse.mdown then 0.2 else 1.) 10 | ] 11 | 12 | let update _computer () = 13 | () 14 | 15 | let app = 16 | game view update () 17 | 18 | let main = Playground_platform.run_app app 19 | -------------------------------------------------------------------------------- /examples/Picture.ml: -------------------------------------------------------------------------------- 1 | (* from https://elm-lang.org/examples/picture *) 2 | open Playground 3 | 4 | let app = 5 | picture [ 6 | rectangle brown 40. 200. 7 | |> move_down 80.; 8 | circle green 100. 9 | |> move_up 100.; 10 | ] 11 | 12 | let main = Playground_platform.run_app app 13 | -------------------------------------------------------------------------------- /examples/Smiley.ml: -------------------------------------------------------------------------------- 1 | (* from elm-playground/examples.smiley.elm *) 2 | open Playground 3 | 4 | let app = 5 | picture [ 6 | circle lightYellow 200.; 7 | 8 | (* left eye *) 9 | circle white 50. 10 | |> move_left 70. 11 | |> move_up 50.; 12 | circle black 10. 13 | |> move_left 75. 14 | |> move_up 40.; 15 | 16 | (* right eye *) 17 | circle white 50. 18 | |> move_right 70. 19 | |> move_up 50.; 20 | circle black 10. 21 | |> move_right 65. 22 | |> move_up 40.; 23 | 24 | (* mouth *) 25 | oval black 180. 40. 26 | |> move_down 100.; 27 | oval lightYellow 180. 40. 28 | |> move_down 90.; 29 | 30 | ] 31 | 32 | let main = Playground_platform.run_app app 33 | -------------------------------------------------------------------------------- /examples/Turtle.ml: -------------------------------------------------------------------------------- 1 | (* from https://elm-lang.org/examples/turtle *) 2 | open Playground 3 | open Basics 4 | 5 | type turtle = { 6 | x: number; 7 | y: number; 8 | angle: number; 9 | } 10 | 11 | let initial_turtle = { x = 0.; y = 0.; angle = 0. } 12 | 13 | let view computer turtle = 14 | [ rectangle blue computer.screen.width computer.screen.height; 15 | rectangle yellow 100. 100.; 16 | image 96. 96. "https://elm-lang.org/images/turtle.gif" 17 | (*"/tmp/turtle.gif" *) 18 | |> move turtle.x turtle.y 19 | |> rotate turtle.angle 20 | |> scale 2. 21 | ] 22 | 23 | let update computer turtle = 24 | { x = turtle.x + 25 | to_y computer.keyboard * cos (degrees_to_radians turtle.angle); 26 | y = turtle.y + 27 | to_y computer.keyboard * sin (degrees_to_radians turtle.angle); 28 | angle = turtle.angle - to_x computer.keyboard 29 | } 30 | 31 | let app = game view update initial_turtle 32 | 33 | let main = Playground_platform.run_app app 34 | -------------------------------------------------------------------------------- /examples/Words.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | let app = 4 | picture [ 5 | rectangle green 10. 10. |> scale 2. |> move 20. 20. |> rotate 45.; 6 | rectangle black 2. 2.; 7 | words green "foobar" |> scale 2. |> rotate 90.; 8 | ] 9 | 10 | let main = Playground_platform.run_app app 11 | -------------------------------------------------------------------------------- /examples/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names 3 | Picture Smiley Words Misc 4 | Animation 5 | Mouse Keyboard Turtle Mario 6 | ) 7 | (libraries 8 | elm_playground 9 | elm_playground_native ; implem of virtual elm_playground 10 | ) 11 | ) 12 | -------------------------------------------------------------------------------- /examples_js/Animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Animation.ml: -------------------------------------------------------------------------------- 1 | ../examples/Animation.ml -------------------------------------------------------------------------------- /examples_js/Keyboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Keyboard.ml: -------------------------------------------------------------------------------- 1 | ../examples/Keyboard.ml -------------------------------------------------------------------------------- /examples_js/Mario.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Mario.ml: -------------------------------------------------------------------------------- 1 | ../examples/Mario.ml -------------------------------------------------------------------------------- /examples_js/Mouse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Mouse.ml: -------------------------------------------------------------------------------- 1 | ../examples/Mouse.ml -------------------------------------------------------------------------------- /examples_js/Picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Picture.ml: -------------------------------------------------------------------------------- 1 | ../examples/Picture.ml -------------------------------------------------------------------------------- /examples_js/Smiley.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Smiley.ml: -------------------------------------------------------------------------------- 1 | ../examples/Smiley.ml -------------------------------------------------------------------------------- /examples_js/Template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Turtle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Turtle.ml: -------------------------------------------------------------------------------- 1 | ../examples/Turtle.ml -------------------------------------------------------------------------------- /examples_js/Words.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples_js/Words.ml: -------------------------------------------------------------------------------- 1 | ../examples/Words.ml -------------------------------------------------------------------------------- /examples_js/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names 3 | Smiley Picture Words 4 | Animation 5 | Mouse Keyboard 6 | Turtle Mario 7 | ) 8 | (libraries 9 | elm_playground 10 | elm_playground_web ; implem of virtual elm_playground 11 | ) 12 | (modes js) 13 | (link_flags -no-check-prims) 14 | ) 15 | -------------------------------------------------------------------------------- /examples_js/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /games/Asteroid.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | (*****************************************************************************) 4 | (* Prelude *) 5 | (*****************************************************************************) 6 | (* Port of the Asteroid clone described at 7 | * http://www.informatik.uni-bremen.de/~clueth/haskell-in-space/ 8 | * but using OCaml instead of Haskell, and using Playground instead of the 9 | * Hugs Graphics Library (HGL). 10 | * 11 | * See https://en.wikipedia.org/wiki/Asteroids_(video_game) for more 12 | * information on Asteroids, or its ancestor 13 | * Spacewar https://en.wikipedia.org/wiki/Spacewar! 14 | * 15 | * 16 | * TODO: 17 | * - see Elm clones of asteroids: 18 | *) 19 | 20 | (*****************************************************************************) 21 | (* Geometry *) 22 | (*****************************************************************************) 23 | 24 | (* todo? put that in a separate Geometry.ml library? with better types 25 | * less: advanced geometry types: 26 | * https://www.cs.cornell.edu/~asampson/media/papers/gator-oopsla2020-preprint.pdf 27 | *) 28 | 29 | (* orig: pad: would be simpler to use float everywhere? *) 30 | type point = {x: int; y: int } 31 | 32 | (* a vector is represented as an arrow from the origin (0, 0) to point *) 33 | type vector = point 34 | 35 | (* 36 | let (point_rotate: number -> point -> point) = fun w p -> 37 | let x' = float p.x in 38 | let y' = float p.y in 39 | { x = Basics.round (x' *. cos w +. y' *. sin w); 40 | y = Basics.round (-. x' *. sin w +. y' *. cos w); 41 | } 42 | *) 43 | 44 | let (vector: point -> point -> vector) = fun p1 p2 -> 45 | { x = p2.x - p1.x; y = p2.y - p1.y } 46 | 47 | let (vector_length: vector -> number) = fun v -> 48 | sqrt (float v.x ** 2. +. float v.y ** 2.) 49 | 50 | let (vector_add: vector -> vector -> vector) = fun v1 v2 -> 51 | {x = v1.x + v2.x; y = v1.y + v2.y } 52 | 53 | (* orig: was calling point_rotate but simpler to do directly *) 54 | let (polar: number -> number -> vector) = fun r phi -> 55 | { x = Basics.round (r *. cos phi); y = Basics.round (r *. sin phi) } 56 | 57 | 58 | (* orig: we can reuse Playground.shape *) 59 | type figure = Playground.shape 60 | 61 | (* Final "resolved" coordinates of a figure after translation/rotate. 62 | * Used for collision detection. 63 | *) 64 | type resolved_shape = 65 | (* TODO | Poly of point list *) 66 | | Circle of point * number (* radius *) 67 | 68 | let (intersect: resolved_shape -> resolved_shape -> bool) = fun x1 x2 -> 69 | match x1, x2 with 70 | | Circle (c1, r1), Circle (c2, r2) -> 71 | let v = vector c1 c2 in 72 | vector_length v <= r1 +. r2 73 | 74 | let (contains: resolved_shape -> point -> bool) = fun x p -> 75 | match x with 76 | | Circle (c, r) -> 77 | let v = vector c p in 78 | vector_length v <= r 79 | 80 | (*****************************************************************************) 81 | (* Model *) 82 | (*****************************************************************************) 83 | 84 | type 'a obj = { 85 | (* current state *) 86 | pos: point; 87 | velocity: vector; 88 | 89 | (* todo: resolved_shape at some point 90 | * orig: was shape, but simpler to store the figure and compute 91 | * the resolved shape when we need to. 92 | *) 93 | figure: figure; 94 | 95 | (* only used for the ship *) 96 | orientation: number; 97 | 98 | xtra: 'a; 99 | } 100 | 101 | 102 | (* simpler to make mutable *) 103 | type ship = { 104 | mutable thrust: number; 105 | mutable h_acceleration: number; 106 | } 107 | (* accelereration delta *) 108 | let a_delta = 1. 109 | (* turn delta *) 110 | let h_delta = 0.3 111 | (* Max velocity *) 112 | let v_max = 20. 113 | 114 | (* when drawn horizontally at 0 degrees *) 115 | let space_ship c = 116 | polygon c [(15., 0.); (-15., 10.); (-10., 0.); (-15., -10.); (15., 0.)] 117 | 118 | 119 | 120 | type bullet = { 121 | cnt: int; 122 | } 123 | let v_bullet = 30. 124 | (* number of tick to live *) 125 | let bullet_TTL = 20 126 | 127 | let space_bullet = 128 | circle red 2. 129 | 130 | let new_bullet ship = 131 | { pos = ship.pos; velocity = polar v_bullet ship.orientation; 132 | orientation = 0.; 133 | figure = space_bullet; 134 | xtra = { cnt = 0 } 135 | } 136 | 137 | type asteroid = { 138 | size: asteroid_size; 139 | } 140 | and asteroid_size = ALarge | AMedium | AWee 141 | 142 | let v_asteroid = 5. 143 | 144 | let random_range (low, high) = 145 | let diff = high -. low in 146 | let n = Random.float diff in 147 | n +. low 148 | 149 | let random_range_int (low, high) = 150 | let diff = abs (high - low) + 1 in 151 | let n = Random.int diff in 152 | n + low 153 | 154 | let space_asteroid () = 155 | let corners = random_range (4., 8.) in 156 | let increment_angle = Basics.pi2 /. corners in 157 | let rec aux angle = 158 | if angle >= Basics.pi2 159 | then [] 160 | else polar (random_range (30., 50.)) angle 161 | ::aux (angle +. increment_angle) 162 | in 163 | let pts = aux increment_angle |> List.map (fun pt -> 164 | float pt.x, float pt.y 165 | ) in 166 | (* Common.pr2_gen pts; *) 167 | polygon (Color.Rgb (100, 100, 100)) pts 168 | 169 | let new_asteroid screen = 170 | let pos = { 171 | x = int_of_float (random_range (screen.left, screen.right)); 172 | y = int_of_float (random_range (screen.bottom, screen.top)); 173 | } in 174 | let velocity = { 175 | x = int_of_float (random_range (-. v_asteroid, v_asteroid)); 176 | y = int_of_float (random_range (-. v_asteroid, v_asteroid)); 177 | } in 178 | { pos; velocity; orientation = 0.; 179 | figure = space_asteroid (); 180 | xtra = { size = ALarge }; 181 | } 182 | 183 | type state = Play | Stop 184 | 185 | type model = { 186 | ship: ship obj; 187 | bullets: bullet obj list; 188 | asteroids: asteroid obj list; 189 | 190 | state: state; 191 | last_tick: float; 192 | } 193 | 194 | (* 30 ms in original program *) 195 | let tick = 0.030 196 | 197 | let initial_model = { 198 | ship = { 199 | pos = { x = 0; y = 0 }; 200 | velocity = { x = 0; y = 0}; 201 | figure = space_ship blue; 202 | orientation = Basics.pi /. 2.; 203 | xtra = { 204 | thrust = 0.; 205 | h_acceleration = 0.; 206 | } 207 | }; 208 | bullets = []; 209 | asteroids = [ 210 | new_asteroid initial_computer.screen; 211 | new_asteroid initial_computer.screen; 212 | new_asteroid initial_computer.screen; 213 | new_asteroid initial_computer.screen; 214 | new_asteroid initial_computer.screen; 215 | ]; 216 | state = Play; 217 | last_tick = Unix.gettimeofday(); 218 | } 219 | 220 | (*****************************************************************************) 221 | (* Collision detection *) 222 | (*****************************************************************************) 223 | 224 | let (resolved_shape_of_obj: 'a obj -> resolved_shape) = 225 | fun { figure = _; pos; orientation = _; _ } -> 226 | (* TODO *) 227 | Circle (pos, 10.) 228 | 229 | let ship_crashed model = 230 | let ship = model.ship in 231 | let shp1 = resolved_shape_of_obj ship in 232 | let shapes = model.asteroids |> List.map resolved_shape_of_obj in 233 | shapes |> List.exists (fun shp2 -> intersect shp1 shp2) 234 | 235 | let directions v = 236 | let n = 1 + Random.int 3 in 237 | let rec aux n = 238 | if n = 0 239 | then [] 240 | else 241 | { x = v.x + random_range_int (- v.x, v.x); 242 | y = v.y + random_range_int (- v.y, v.y); 243 | }::aux (n - 1) 244 | in 245 | aux n 246 | 247 | let explode a dirs = 248 | match a.xtra.size with 249 | | ALarge -> 250 | dirs |> List.map (fun velocity -> 251 | { a with figure = a.figure |> scale 0.5; velocity; 252 | xtra = { size = AMedium } 253 | }) 254 | | AMedium -> 255 | dirs |> List.map (fun velocity -> 256 | { a with figure = a.figure |> scale 0.75; velocity; 257 | xtra = { size = AWee }}) 258 | | AWee -> [] 259 | 260 | 261 | let (check_asteroids: model -> asteroid obj list) = 262 | fun model -> 263 | let bullets = model.bullets |> List.map (fun x -> x.pos) in 264 | model.asteroids |> List.map (fun a -> 265 | let shp = resolved_shape_of_obj a in 266 | if bullets |> List.exists (fun p -> contains shp p) 267 | then 268 | let dirs = directions a.velocity in 269 | explode a dirs 270 | else [a] 271 | ) |> List.flatten 272 | 273 | 274 | (*****************************************************************************) 275 | (* View *) 276 | (*****************************************************************************) 277 | 278 | (* todo: resolved_shape_of_obj at some point *) 279 | let (shape_of_obj: 'a obj -> shape) = 280 | fun { figure; pos; orientation; _ } -> 281 | figure 282 | |> rotate (Basics.radians_to_degrees orientation) 283 | |> move (float pos.x) (float pos.y) 284 | 285 | let view model = 286 | shape_of_obj model.ship :: 287 | (List.map shape_of_obj model.bullets) @ 288 | (List.map shape_of_obj model.asteroids) 289 | 290 | (*****************************************************************************) 291 | (* Update *) 292 | (*****************************************************************************) 293 | 294 | type msg = 295 | | Tick of float 296 | 297 | | MoveLeft 298 | | MoveRight 299 | | StopMove 300 | 301 | | Accelerate of bool 302 | 303 | | Shoot 304 | 305 | | Noop 306 | 307 | let msg_of_key_down = function 308 | | "ArrowLeft" -> MoveLeft 309 | | "ArrowRight" -> MoveRight 310 | | "ArrowUp" -> Accelerate true 311 | | "space" -> Shoot 312 | | _ -> Noop 313 | 314 | let msg_of_key_up = function 315 | | "ArrowLeft" -> StopMove 316 | | "ArrowRight" -> StopMove 317 | | "ArrowUp" -> Accelerate false 318 | | "space" -> Noop 319 | | _ -> Noop 320 | 321 | 322 | (* orig: could use modulo if the origin was not at the center on the screen *) 323 | let add_modulo_window screen pos velocity = 324 | let { x; y } = vector_add pos velocity in 325 | let x = float x in let y = float y in 326 | (* there is probably something simpler than this code ... *) 327 | let x = int_of_float ( 328 | match () with 329 | | _ when x > screen.right -> 330 | x -. screen.right +. screen.left 331 | | _ when x < screen.left -> 332 | screen.right -. (screen.left -. x) 333 | | _ -> x 334 | ) 335 | in 336 | let y = int_of_float ( 337 | match () with 338 | | _ when y > screen.top -> 339 | y -. screen.top +. screen.bottom 340 | | _ when y < screen.bottom -> 341 | screen.top -. (screen.bottom -. y) 342 | | _ -> y 343 | ) 344 | in 345 | { x; y } 346 | 347 | (* orig: this assumed to be called every tick of 30ms *) 348 | let move_ship screen 349 | ({ pos; velocity; orientation; xtra = { h_acceleration; thrust};_} as ship)= 350 | let new_velocity = vector_add (polar thrust orientation) velocity in 351 | let l = vector_length new_velocity in 352 | 353 | { ship with 354 | pos = add_modulo_window screen pos velocity ; 355 | velocity = if l > v_max then failwith "Todo" else new_velocity; 356 | orientation = orientation +. h_acceleration; 357 | } 358 | 359 | let move_bullet screen ({ pos; velocity; xtra = { cnt }; _ } as bullet) = 360 | { bullet with 361 | pos = add_modulo_window screen pos velocity; 362 | xtra = { cnt = cnt + 1 }; 363 | } 364 | let move_bullets screen xs = 365 | xs 366 | |> List.map (move_bullet screen) 367 | |> List.filter (fun b -> b.xtra.cnt < bullet_TTL) 368 | 369 | let move_asteroid screen ({ pos; velocity; _ } as asteroid) = 370 | { asteroid with pos = add_modulo_window screen pos velocity; } 371 | 372 | let move_asteroids screen xs = 373 | xs 374 | |> List.map (move_asteroid screen) 375 | 376 | let update msg model = 377 | (match msg with 378 | | Noop -> model 379 | | Tick now -> 380 | 381 | let delta = now -. model.last_tick in 382 | 383 | if delta < tick || model.state = Stop 384 | then model 385 | else 386 | let model = { model with 387 | ship = move_ship initial_computer.screen model.ship; 388 | bullets = move_bullets initial_computer.screen model.bullets; 389 | asteroids = move_asteroids initial_computer.screen model.asteroids; 390 | last_tick = now; 391 | } in 392 | let asteroids = check_asteroids model in 393 | let state = 394 | if ship_crashed model 395 | then Stop 396 | else Play 397 | in 398 | { model with state; asteroids } 399 | 400 | | Shoot -> 401 | let ship = model.ship in 402 | { model with bullets = new_bullet ship::model.bullets } 403 | 404 | | MoveLeft -> 405 | (* simpler when using mutable *) 406 | model.ship.xtra.h_acceleration <- 1. *. h_delta; 407 | model 408 | 409 | | MoveRight -> 410 | model.ship.xtra.h_acceleration <- -1. *. h_delta; 411 | model 412 | 413 | | StopMove -> 414 | model.ship.xtra.h_acceleration <- 0.; 415 | model 416 | | Accelerate b -> 417 | model.ship.xtra.thrust <- if b then a_delta else 0.; 418 | model 419 | ), Cmd.none 420 | 421 | (*****************************************************************************) 422 | (* Entry point *) 423 | (*****************************************************************************) 424 | 425 | let app = { Playground. 426 | view; 427 | update; 428 | init = (fun () -> (initial_model), Cmd.none); 429 | subscriptions = (fun _ -> Sub.batch [ 430 | Sub.on_animation_frame (fun x -> Tick x); 431 | Sub.on_key_down (fun key -> msg_of_key_down key); 432 | Sub.on_key_up (fun key -> msg_of_key_up key); 433 | ]); 434 | } 435 | 436 | let main = 437 | Playground_platform.run_app app 438 | -------------------------------------------------------------------------------- /games/Pong.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | open Basics (* float arithmetic operators by default *) 3 | 4 | (*****************************************************************************) 5 | (* Prelude *) 6 | (*****************************************************************************) 7 | (* Port of the Pong clone described at https://elm-lang.org/news/making-pong 8 | * but using OCaml instead of Elm, and using Playground instead of Collage. 9 | * 10 | * See https://en.wikipedia.org/wiki/Pong for more information on Pong. 11 | * 12 | * TODO: 13 | * - http://mathieu.agopian.info/blog/making-a-pong-game-in-elm.html (2019) 14 | * - https://github.com/einars/skapong ocaml one 15 | * - https://www.awesomeinc.org/tutorials/unity-pong/ unity! with 16 | * physics engine (friction, bounciness) 17 | * - https://en.scratch-wiki.info/wiki/Pong 18 | * - https://www.101computing.net/pong-tutorial-using-pygame-getting-started/ 19 | * 20 | * TODO: extensions listed at the end of the making-pong blog post: 21 | * - dotted line mid field 22 | * - ability to pause the game 23 | * - ability to reset the game (menu somewhere? restart button between 24 | * the score) 25 | * - make ball collisions more complicated: 26 | * * when the ball hits the corner of a paddle, it changes direction 27 | * * if the ball hits a moving paddle, it adds spin to the ball, making it 28 | * rebound in a different direction 29 | * - add a second ball to the game 30 | * - write a simple AI for a paddle (easy but boring = AI that try to 31 | * stay at the same y than the ball; but could design less boring but 32 | * more fun AI too). 33 | *) 34 | 35 | (*****************************************************************************) 36 | (* Model *) 37 | (*****************************************************************************) 38 | 39 | (* was time in Pong.elm, but playground defines time as Time of posix *) 40 | type delta = float 41 | 42 | type obj = { 43 | x: number; 44 | y: number; 45 | vx: number; 46 | vy: number; 47 | } 48 | 49 | type ball = obj 50 | 51 | type player = { 52 | obj: obj; 53 | score: int; 54 | } 55 | let player x = 56 | { obj = { (* should remain constant *) 57 | x; 58 | y = 0.; 59 | (* will remain 0 *) 60 | vx = 0.; 61 | vy = 0. }; 62 | score = 0 } 63 | 64 | type state = Play | Pause 65 | 66 | type game = { 67 | state: state; 68 | ball: ball; 69 | player1: player; 70 | player2: player; 71 | } 72 | 73 | (* coupling: with Playground.initial_computer.screen at 600 x 600 *) 74 | let (game_width, game_height) = (600., 400.) 75 | let (half_width, half_height) = (300., 200.) 76 | 77 | (* This means that in 1 second, the object will move 200 pixels. 78 | * note: was duplicated many times in the blog post, better to factorize. 79 | * note that this is both the velocity of the ball and the paddle, so the 80 | * paddle can not move faster than the ball :) 81 | *) 82 | let default_velocity = 200. 83 | 84 | let default_game = { 85 | state = Pause; 86 | ball = { x = 0.; y = 0.; vx = default_velocity; vy = default_velocity }; 87 | (* player1 is on the left, player2 on the right *) 88 | player1 = player (-. half_width + 20.); 89 | player2 = player ( half_width - 20.); 90 | } 91 | 92 | (*****************************************************************************) 93 | (* View *) 94 | (*****************************************************************************) 95 | let pong_green = Color.Rgb (60, 100, 60) 96 | let text_green = Color.Rgb (160, 200, 160) 97 | 98 | let msg = "SPACE to start, w/s and up/down to move" 99 | 100 | let display_obj obj shape = 101 | shape |> move (obj.x) (obj.y) 102 | 103 | let view _computer (game, _last_tick) = 104 | [ rectangle pong_green game_width game_height; 105 | 106 | display_obj game.ball (oval white 15. 15.); 107 | 108 | display_obj game.player1.obj (rectangle white 10. 40.); 109 | display_obj game.player2.obj (rectangle white 10. 40.); 110 | 111 | (let s = Printf.sprintf "%d %d" game.player1.score game.player2.score in 112 | words text_green s |> scale 10. |> move 0. (game_height / 2. - 40.)); 113 | 114 | (let s = if game.state = Play then "" else msg in 115 | words text_green s |> scale 2. |> move 0. (40. - game_height / 2.)); 116 | ] 117 | 118 | (*****************************************************************************) 119 | (* Update *) 120 | (*****************************************************************************) 121 | (* are n and m near each other, specifically are they within c of each other *) 122 | let near n c m = 123 | m >= n - c && m <= n + c 124 | 125 | (* Is the ball within a paddle? 126 | * coupling: 8 is (a little more than) half of oval width (15) of the ball 127 | * and 20 is half the height of the player paddle (40) 128 | *) 129 | let (within: ball -> player -> bool) = fun ball player -> 130 | near player.obj.x 8. ball.x && 131 | near player.obj.y 20. ball.y 132 | 133 | (* change the direction of a velocity (vx, or vy) based on collisions *) 134 | let (step_v: number -> bool -> bool -> number) = 135 | fun v lower_collision upper_collision -> 136 | match () with 137 | (* bottom or left collision *) 138 | | _ when lower_collision -> abs_float v 139 | (* top or right collision *) 140 | | _ when upper_collision -> -. (abs_float v) 141 | | _ -> v 142 | 143 | let (step_obj: delta -> obj -> obj) = fun t ({ x; y; vx; vy} as obj) -> 144 | { obj with x = x + vx * t; y = y + vy * t } 145 | 146 | (* move a ball forward, detecting collisions with either paddle *) 147 | let (step_ball: delta -> ball -> player -> player -> ball) = 148 | fun t ({x = _; y; vx; vy} as ball) player1 player2 -> 149 | (* put back at the center of the screen when reach player side *) 150 | if not (near 0. half_width ball.x) 151 | then { ball with x = 0.; y = 0. } 152 | else 153 | step_obj t { ball with 154 | vx = step_v vx (within ball player1) (within ball player2); 155 | (* coupling: 7. =~ half size of ball *) 156 | vy = step_v vy (y < -. half_height + 7.) (y > half_height - 7.); 157 | } 158 | 159 | (* step a player forward, making sure it does not fly off the screen *) 160 | let (step_player: delta -> number -> int -> player -> player) = 161 | fun t dir points player -> 162 | let obj' = 163 | (* bugfix: vy here! not vx *) 164 | step_obj t { player.obj with vy = dir * default_velocity } in 165 | let y' = Basics.clamp (-. half_height + 22.) (half_height - 22.) obj'.y in 166 | let score' = player.score +.. points in 167 | { obj = { obj' with y = y'}; score = score' } 168 | 169 | type input = { 170 | space: bool; 171 | 172 | (* -1, 0, 1 *) 173 | paddle1: number; 174 | paddle2: number; 175 | 176 | delta: delta; 177 | } 178 | 179 | let input_of_computer computer = 180 | let kbd = computer.keyboard in 181 | { space = kbd.kspace; 182 | (* bugfix: player2 is on the right, so the arrows or for player2 *) 183 | paddle1 = to_y2 kbd; 184 | paddle2 = to_y kbd; 185 | delta = 0.; 186 | } 187 | 188 | let (step_game: input -> game -> game) = fun input game -> 189 | let { space; paddle1; paddle2; delta} = input in 190 | let {state; ball; player1; player2} = game in 191 | 192 | (* ball on the right of player2 => score for player 1 *) 193 | let score1 = if ball.x > half_width then 1 else 0 in 194 | (* ball on left of player1 => score for player 2 *) 195 | let score2 = if ball.x < -. half_width then 1 else 0 in 196 | 197 | let state' = 198 | match () with 199 | | _ when space -> Play 200 | | _ when score1 <> score2 -> Pause 201 | | _ -> state 202 | in 203 | let ball' = 204 | if state = Pause 205 | then ball 206 | else step_ball delta ball player1 player2 207 | in 208 | let player1' = step_player delta paddle1 score1 player1 in 209 | let player2' = step_player delta paddle2 score2 player2 in 210 | { state = state'; ball = ball'; player1 = player1'; player2 = player2' } 211 | 212 | let update computer (game, last_tick) = 213 | let input = input_of_computer computer in 214 | let (Time now) = computer.time in 215 | let delta = now - last_tick in 216 | let game' = step_game { input with delta } game in 217 | game', now 218 | 219 | (*****************************************************************************) 220 | (* Entry point *) 221 | (*****************************************************************************) 222 | 223 | let app = 224 | game view update (default_game, Unix.gettimeofday()) 225 | 226 | let main = 227 | Playground_platform.run_app app 228 | -------------------------------------------------------------------------------- /games/Snake.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | (*****************************************************************************) 4 | (* Prelude *) 5 | (*****************************************************************************) 6 | (* Port of the Snake clone https://github.com/amarantedaniel/snek, 7 | * but using OCaml instead of Elm, and using Playground instead of HTML/SVG. 8 | * 9 | * See https://en.wikipedia.org/wiki/Snake_(video_game_genre) for more info. 10 | * 11 | * TODO: 12 | * - two players (like in original Snake game called Blockade) 13 | * - display score 14 | * - accelerate games as times goes 15 | * - high score table 16 | *) 17 | 18 | (*****************************************************************************) 19 | (* Model *) 20 | (*****************************************************************************) 21 | 22 | (* The origin of the grid (0, 0) is at the bottom left of the screen. 23 | * This is different from the coordinate system of Playground where the 24 | * origin is at the center of the screen, but it allows to use 'mod' 25 | * to easily move the snake around the edges. 26 | *) 27 | type position = (int * int) (* x, y *) 28 | 29 | type grid_size = { 30 | g_width: int; 31 | g_height: int; 32 | } 33 | (* less: could be changed *) 34 | let grid_size = { g_width = 20; g_height = 20 } 35 | 36 | let cell_size screen = 37 | int_of_float screen.width / grid_size.g_width 38 | 39 | (* TODO: do not return a position already used by the snake *) 40 | let random_position () = 41 | (Random.int (grid_size.g_width - 1), Random.int (grid_size.g_height - 1)) 42 | 43 | type direction = Up | Down | Left | Right 44 | 45 | (* using mutable so easier to update subparts of the model *) 46 | type snake = { 47 | mutable head: position; 48 | mutable body: position list; 49 | mutable direction: direction; 50 | } 51 | let initial_snake = { 52 | head = (3, 0); 53 | body = [(2, 0); (1, 0); (1, 0)]; 54 | direction = Right; 55 | } 56 | 57 | 58 | type model = { 59 | snake: snake; 60 | mutable food: position; 61 | mutable game_over: bool; 62 | mutable last_tick: Time.posix; 63 | } 64 | let initial_model = { 65 | snake = initial_snake; 66 | food = (grid_size.g_width / 2, grid_size.g_height / 2); 67 | game_over = false; 68 | last_tick = 0.; 69 | } 70 | 71 | (*****************************************************************************) 72 | (* Helpers *) 73 | (*****************************************************************************) 74 | 75 | let rec list_init = function 76 | | [] -> raise Not_found 77 | | [ _x ] -> [] 78 | | x :: y :: xs -> x :: list_init (y :: xs) 79 | 80 | (*****************************************************************************) 81 | (* View *) 82 | (*****************************************************************************) 83 | let f = float 84 | let i = int_of_float 85 | let smaller size = f size *. 0.90 86 | 87 | let movei a b shape = move (f a) (f b) shape 88 | 89 | (* TODO: this currently assumes a square screen *) 90 | let translate (x,y) screen shape = 91 | let cell_size = cell_size screen in 92 | shape 93 | |> move screen.left screen.bottom 94 | |> movei (x * cell_size) (y * cell_size) 95 | |> movei (cell_size / 2) (cell_size / 2) 96 | 97 | 98 | let view_background screen = 99 | [rectangle (Color.Hex "#8cbf00") screen.width screen.height] 100 | 101 | let view_food screen pos = 102 | let size = cell_size screen in 103 | let radius = size / 3 in 104 | [circle gray (f radius) |> translate pos screen; 105 | circle black (smaller radius) |> translate pos screen; 106 | ] 107 | 108 | let view_snake_part screen pos = 109 | let size = cell_size screen in 110 | [square gray (f size) |> translate pos screen; 111 | square black (smaller size) |> translate pos screen; 112 | ] 113 | 114 | let view_snake screen snake = 115 | List.map (view_snake_part screen) (snake.head::snake.body) |> List.flatten 116 | 117 | let view_game_over _screen = 118 | [ words red "GAME OVER" |> scale 10. ] 119 | 120 | let view computer model = 121 | let screen = computer.screen in 122 | 123 | view_background screen @ 124 | view_snake screen model.snake @ 125 | view_food screen model.food @ 126 | (if model.game_over then view_game_over screen else []) 127 | 128 | 129 | (*****************************************************************************) 130 | (* Update *) 131 | (*****************************************************************************) 132 | let compute_new_head snake = 133 | let (x, y) = snake.head in 134 | let h = grid_size.g_height in 135 | let w = grid_size.g_width in 136 | match snake.direction with 137 | | Up -> (x, (y + 1 ) mod h) 138 | | Down -> (x, (y - 1 + h) mod h) 139 | | Right -> ((x + 1) mod w, y) 140 | | Left -> ((x - 1 + w) mod w, y) 141 | 142 | let update_direction kbd snake = 143 | let new_dir = 144 | match () with 145 | | _ when kbd.kup -> Up 146 | | _ when kbd.kdown -> Down 147 | | _ when kbd.kleft -> Left 148 | | _ when kbd.kright -> Right 149 | | _ -> snake.direction 150 | in 151 | let new_dir = 152 | match new_dir, snake.direction with 153 | (* invalid transitions *) 154 | | Left, Right | Right, Left 155 | | Up, Down | Down, Up 156 | -> snake.direction 157 | | x, _ -> x 158 | in 159 | snake.direction <- new_dir 160 | 161 | let update computer model = 162 | let (Time now) = computer.time in 163 | (* operate by side effect on the model; simpler *) 164 | if now -. model.last_tick > 0.5 165 | then begin 166 | model.last_tick <- now; 167 | let snake = model.snake in 168 | let new_head = compute_new_head snake in 169 | let ate_food = new_head = model.food in 170 | let new_body = 171 | if ate_food 172 | then snake.body 173 | else list_init snake.body 174 | in 175 | snake.body <- snake.head::new_body; 176 | snake.head <- new_head; 177 | if ate_food 178 | then model.food <- random_position (); 179 | model.game_over <- List.mem new_head new_body; 180 | end; 181 | update_direction computer.keyboard model.snake; 182 | 183 | model 184 | 185 | (*****************************************************************************) 186 | (* Entry point *) 187 | (*****************************************************************************) 188 | 189 | let app = 190 | game view update initial_model 191 | 192 | let main = 193 | Random.self_init (); 194 | Playground_platform.run_app app 195 | -------------------------------------------------------------------------------- /games/Tetris.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | (*****************************************************************************) 4 | (* Prelude *) 5 | (*****************************************************************************) 6 | (* Port of the Tetris clone https://github.com/w0rm/elm-flatris 7 | * (itself a clone of https://github.com/skidding/flatris, 8 | * itself a clone of the venerable Tetris), 9 | * but using OCaml instead of Elm, and using Playground instead of HTML/SVG. 10 | * 11 | * See https://en.wikipedia.org/wiki/Tetris for more information on Tetris. 12 | * 13 | * extensions I've added: 14 | * - space key to drop to the bottom the piece 15 | * 16 | * TODO finish porting of elm-flatris: 17 | * - check end game 18 | * - do not display piece when out of the well 19 | * - handle Pause/Resume/Stopped 20 | * - more complex input management where keeping the key pressed has 21 | * an effect, rather that forcing the user to keyup 22 | * (called confusingly "animation") 23 | * - accelerate Tick as you clear more lines, manage "level" score 24 | * TODO: 25 | * - sound when line cleared! 26 | *) 27 | 28 | (*****************************************************************************) 29 | (* Grid and pieces (model part 1) *) 30 | (*****************************************************************************) 31 | type color = Color.t 32 | 33 | (* pad: I introduced this type. 34 | * The origin (0, 0) in the grid is at the top left corner of the tetris "well". 35 | * Pos can also have a negative y, meaning it is not yet visible. 36 | *) 37 | type pos = {x: int; y: int } 38 | 39 | 40 | (* orig: was polymorphic, but always use with color, so simpler to hardcode *) 41 | type cell = { 42 | color: color; 43 | pos: pos; 44 | } 45 | 46 | (* an empty cell is represented as a non-existing cell. 47 | * alt: cell option array. 48 | *) 49 | type grid = cell list 50 | 51 | let empty_grid = [] 52 | 53 | 54 | (* pad: I introduced this type. 55 | * note that x and y are between 0 and 3 for a piece. 56 | *) 57 | type piece = grid 58 | 59 | (*****************************************************************************) 60 | (* General Helpers *) 61 | (*****************************************************************************) 62 | (* those functions were in my common2.ml before, could put in a core/List_.ml *) 63 | 64 | let sum xs = List.fold_left ( + ) 0 xs 65 | 66 | let foldl1 p xs = 67 | match xs with 68 | | x :: xs -> List.fold_left p x xs 69 | | [] -> failwith "foldl1: empty list" 70 | 71 | let maximum l = foldl1 max l 72 | 73 | let exclude p xs = List.filter (fun x -> not (p x)) xs 74 | 75 | (*****************************************************************************) 76 | (* Helpers *) 77 | (*****************************************************************************) 78 | 79 | let (center_of_mass: piece -> pos) = fun piece -> 80 | let len = float (List.length piece) in 81 | let xs = piece |> List.map (fun cell -> cell.pos.x) in 82 | let ys = piece |> List.map (fun cell -> cell.pos.y) in 83 | { x = Basics.round (float (sum xs) /. len); 84 | y = Basics.round (float (sum ys) /. len); 85 | } 86 | 87 | let (init_position: int -> piece -> pos) = fun wid piece -> 88 | let {x; _} = center_of_mass piece in 89 | let y = piece |> List.map (fun cell -> cell.pos.y) |> maximum in 90 | (* -y -1 to set as non visible the bottom of the piece *) 91 | { x = wid / 2 - x; 92 | y = - y - 1 93 | } 94 | 95 | let (size: piece -> (int * int)) = fun piece -> 96 | let xs = piece |> List.map (fun cell -> cell.pos.x) in 97 | let ys = piece |> List.map (fun cell -> cell.pos.y) in 98 | let dim zs = 1 + maximum zs in 99 | dim xs, dim ys 100 | 101 | (* put the piece in the grid *) 102 | let rec (stamp: pos -> piece -> grid -> grid) = fun { x; y} piece grid -> 103 | match piece with 104 | | [] -> grid 105 | | cell::rest -> 106 | let newpos = { x = cell.pos.x + x; y = cell.pos.y + y } in 107 | (* todo: need exclude from grid cells with pos = newpos? *) 108 | stamp { x; y } rest ({ cell with pos = newpos }::grid) 109 | 110 | (* check if the piece collide with the grid in the well (width x height) *) 111 | let rec collide ({x; y} as pos) piece (width, height) grid = 112 | match piece with 113 | | [] -> false 114 | | cell::rest -> 115 | let x = cell.pos.x + x in 116 | let y = cell.pos.y + y in 117 | x >= width || x < 0 || y >= height || 118 | (List.mem { x; y} (grid |> List.map (fun cell -> cell.pos))) || 119 | collide pos rest (width, height) grid 120 | 121 | (* rotate clockwise *) 122 | let (rotate: piece -> piece) = fun piece -> 123 | let { x; y} = center_of_mass piece in 124 | 125 | piece |> List.map (fun cell -> 126 | let pos = cell.pos in 127 | (* ???? *) 128 | { cell with pos = { x = 1 + y - pos.y; y = - x + y + pos.x } } 129 | ) 130 | 131 | let rec full_line_opt width grid = 132 | match grid with 133 | | [] -> None 134 | | cell::_ -> 135 | let line_y = cell.pos.y in 136 | let same_line, other = 137 | grid |> List.partition (fun { pos; _} -> pos.y = line_y) in 138 | if List.length same_line = width 139 | then Some line_y 140 | else full_line_opt width other 141 | 142 | let rec clear_lines width grid = 143 | match full_line_opt width grid with 144 | | None -> grid, 0 145 | | Some line_y -> 146 | let cleared_grid = grid |> exclude (fun {pos; _} -> pos.y = line_y)in 147 | let (above, below) = 148 | cleared_grid |> List.partition (fun {pos; _} -> pos.y < line_y) in 149 | let dropped_above = 150 | above |> List.map (fun cell -> 151 | { cell with pos = { x = cell.pos.x; y = cell.pos.y + 1 } } 152 | ) in 153 | let (new_grid, nb_lines) = clear_lines width (dropped_above @ below) in 154 | (new_grid, nb_lines + 1) 155 | 156 | let (from_list: color -> (int * int) list -> grid) = fun color xs -> 157 | xs |> List.map (fun (x, y) -> { color; pos = {x ; y} }) 158 | 159 | let (tetriminos: piece list) = [ 160 | (* xxxx 161 | *) 162 | Color.Rgb (60, 199, 214), [(0, 0); (1, 0); (2, 0); (3, 0)]; 163 | (* xx 164 | * xx 165 | *) 166 | Color.Rgb (251, 180, 20), [(0, 0); (1, 0); (0, 1); (1, 1)]; 167 | (* xxx 168 | * x 169 | *) 170 | Color.Rgb (176, 68, 151), [(1, 0); (0, 1); (1, 1); (2, 1)]; 171 | (* xxx 172 | * x 173 | *) 174 | Color.Rgb (57, 147, 208), [(0, 0); (0, 1); (1, 1); (2, 1)]; 175 | (* xxx 176 | * x 177 | *) 178 | Color.Rgb (237, 101, 47), [(2, 0); (0, 1); (1, 1); (2, 1)]; 179 | (* xx 180 | * xx 181 | *) 182 | Color.Rgb (149, 196, 61), [(1, 0); (2, 0); (0, 1); (1, 1)]; 183 | (* xx 184 | * xx 185 | *) 186 | Color.Rgb (232, 65, 56), [(0, 0); (1, 0); (1, 1); (2, 1)]; 187 | ] |> List.map (fun (color, xs) -> from_list color xs) 188 | 189 | let random_tetrimino () = 190 | let len = List.length tetriminos in 191 | let n = Random.int len in 192 | List.nth tetriminos n 193 | 194 | (*****************************************************************************) 195 | (* Model *) 196 | (*****************************************************************************) 197 | 198 | (* todo? need Stopped and Paused? *) 199 | type state = 200 | | Stopped 201 | | Playing 202 | | Paused 203 | 204 | (* less: animationState? input *) 205 | type model = { 206 | (* screen size (= Playground.initial_computer.screen) *) 207 | (* size: number * number; *) 208 | 209 | (* grid dimension *) 210 | width: int; 211 | height: int; 212 | 213 | (* current state of the grid *) 214 | grid: grid; 215 | 216 | (* current piece *) 217 | active: piece; 218 | (* leftest highest part of the piece. 219 | * using a float for y allows to handle speed of drop *) 220 | position: (int * float); 221 | (* next piece *) 222 | next: piece; 223 | 224 | score: int; 225 | lines: int; 226 | 227 | state: state; 228 | 229 | last_tick: float; 230 | } 231 | 232 | let spawn_tetrimino model = 233 | let active = model.next in 234 | let next = random_tetrimino () in 235 | let {x; y} = init_position model.width active in 236 | { model with next; active; position = (x, float y) } 237 | 238 | let _init = Random.self_init () 239 | 240 | let initial_model = spawn_tetrimino { 241 | 242 | (* coupling: Playground.initial_computer.screen *) 243 | (* size = (600., 600.); *) 244 | 245 | width = 10; 246 | height = 20; 247 | 248 | score = 0; 249 | lines = 0; 250 | 251 | grid = empty_grid; 252 | 253 | active = empty_grid; 254 | position = (0, 0.); 255 | next = random_tetrimino (); 256 | 257 | state = Stopped; 258 | 259 | last_tick = Unix.gettimeofday(); 260 | } 261 | 262 | (*****************************************************************************) 263 | (* View *) 264 | (*****************************************************************************) 265 | let fl = float 266 | 267 | (* in pixels, height = 20 * 30 = 600 < 768 height in initial_computer.screen *) 268 | let cell_size = 30 269 | 270 | let move_box {width; height; _ } { x; y } shape = 271 | shape 272 | (* go to (0, 0), top left corner of the well *) 273 | |> move_up (fl (height * cell_size / 2)) 274 | |> move_left (fl (width * cell_size / 2)) 275 | (* now move the piece to (x, y) *) 276 | |> move_right (fl (x * cell_size + cell_size / 2)) 277 | |> move_down (fl (y * cell_size + cell_size / 2)) 278 | 279 | let render_box model { color; pos } = 280 | square color (fl cell_size) |> move_box model pos 281 | 282 | let render_well 283 | ({ width; height; position = (posx, posy); active; grid; _ } as model) = 284 | let final_grid = 285 | stamp { x = posx; y = int_of_float (floor posy) } active grid 286 | in 287 | [rectangle (Color.Rgb (236, 240, 241)) 288 | (fl (width * cell_size)) 289 | (fl (height * cell_size)) 290 | ] @ 291 | (final_grid |> List.map (render_box model)) @ 292 | [] 293 | 294 | let render_panel { width; height; score; lines; next; _ } = 295 | let move x = 296 | (* got to top right area *) 297 | x |> move_up (fl (height * cell_size / 2 - 2 * cell_size)) 298 | |> move_right (fl (width * cell_size / 2 + 4 * cell_size)) 299 | in 300 | (* less: bold *) 301 | let title = words (Color.Hex "#34495f") "Flatris" |> move |> scale 5. in 302 | 303 | let move x = x |> move |> move_down (fl (cell_size * 3)) in 304 | let score_label = words (Color.Hex "#bdc3c7") "Score" |> move |> scale 2. in 305 | 306 | let move x = x |> move |> move_down (fl (cell_size * 2)) in 307 | let score = 308 | words (Color.Hex "#3993d0") (string_of_int score) |> move |> scale 3. in 309 | 310 | let move x = x |> move |> move_down (fl (cell_size * 3)) in 311 | let lines_label = 312 | words (Color.Hex "#bdc3c7") "Lines Cleared" |> move |> scale 2. in 313 | 314 | let move x = x |> move |> move_down (fl (cell_size * 2)) in 315 | let lines = 316 | words (Color.Hex "#3993d0") (string_of_int lines) |> move |> scale 3. in 317 | 318 | let move x = x |> move |> move_down (fl (cell_size * 3)) in 319 | let next_label = 320 | words (Color.Hex "#bdc3c7") "Next Shape" |> move |> scale 2. in 321 | 322 | let move x = x |> move |> move_down (fl (cell_size * 2)) in 323 | let { x; _} = center_of_mass next in 324 | let next = 325 | next |> List.map (fun cell -> 326 | square cell.color (fl cell_size) |> move 327 | |> move_left (fl (x * cell_size)) 328 | |> move_right (fl (cell.pos.x * cell_size)) 329 | |> move_down (fl (cell.pos.y * cell_size))) in 330 | 331 | 332 | [title; score_label; score; lines_label; lines; next_label] @ next 333 | 334 | let view model = 335 | render_well model @ 336 | render_panel model @ 337 | [] 338 | 339 | (*****************************************************************************) 340 | (* Update *) 341 | (*****************************************************************************) 342 | type msg = 343 | | Tick of float 344 | 345 | (* less: add bool to mean down/up (on/off) *) 346 | | MoveLeft 347 | | MoveRight 348 | | Rotate 349 | | Accelerate 350 | | FullDrop 351 | 352 | | Noop 353 | 354 | let msg_of_key = function 355 | | "ArrowLeft" -> MoveLeft 356 | | "ArrowRight" -> MoveRight 357 | | "ArrowDown" -> Accelerate 358 | | "ArrowUp" -> Rotate 359 | | "space" -> FullDrop 360 | | _ -> Noop 361 | 362 | let clear_lines_and_add_score model = 363 | let grid, nblines = clear_lines model.width model.grid in 364 | let bonus = 365 | match nblines with 366 | | 0 -> 0 367 | | 1 -> 100 368 | | 2 -> 300 369 | | 3 -> 500 370 | | 4 -> 800 371 | | _ -> failwith "Impossible" 372 | in 373 | { model with 374 | grid; 375 | score = model.score + bonus (* todo: level model *); 376 | lines = model.lines + nblines; 377 | } 378 | 379 | (* drop because Tick or Accelerate *) 380 | let (drop_tetrimino: model -> float -> model * bool) = fun model dy -> 381 | let (x, y) = model.position in 382 | let new_pos = (x, y +. dy) in 383 | if collide { x; y = int_of_float (floor (y +. dy)) } model.active 384 | (model.width, model.height) model.grid 385 | then 386 | let score = List.length model.active in 387 | { model with 388 | grid = stamp { x; y = int_of_float (floor y) } model.active model.grid; 389 | score = model.score + score; 390 | } 391 | |> spawn_tetrimino 392 | |> clear_lines_and_add_score 393 | |> (fun model -> model, true) 394 | else { model with position = new_pos }, false 395 | 396 | let rotate_tetrimino model = 397 | let rotated = rotate model.active in 398 | let (x, y) = model.position in 399 | 400 | (* make sure the rotated tetrimino stays in the grid *) 401 | let rec shift_pos_if_collide deltas = 402 | match deltas with 403 | | dx::rest -> 404 | if collide { x = x+dx; y = int_of_float (floor y) } rotated 405 | (model.width, model.height) model.grid 406 | then shift_pos_if_collide rest 407 | else { model with active = rotated; position = (x + dx, y) } 408 | | [] -> model 409 | in 410 | (* -2 + 2 range should be enough *) 411 | shift_pos_if_collide [0; 1; -1; 2; -2] 412 | 413 | 414 | 415 | 416 | (* less: Resize/GetViewPort *) 417 | let update msg model = 418 | (match msg with 419 | | Tick t -> 420 | let delta = t -. model.last_tick in 421 | let model = { model with last_tick = t } in 422 | let dy = delta in 423 | drop_tetrimino model dy |> fst 424 | 425 | | MoveLeft -> 426 | let (x, y) = model.position in 427 | let dx = - 1 in 428 | if collide { x = x+dx; y = int_of_float (floor y) } model.active 429 | (model.width, model.height) model.grid 430 | then model 431 | else { model with position = (x+dx, y) } 432 | 433 | | MoveRight -> 434 | let (x, y) = model.position in 435 | let dx = + 1 in 436 | if collide { x = x+dx; y = int_of_float (floor y) } model.active 437 | (model.width, model.height) model.grid 438 | then model 439 | else { model with position = (x+dx, y) } 440 | 441 | | Rotate -> 442 | rotate_tetrimino model 443 | 444 | | Accelerate -> 445 | let dy = 1. in 446 | drop_tetrimino model dy |> fst 447 | 448 | | FullDrop -> 449 | let rec aux model = 450 | let (model, at_bottom) = drop_tetrimino model 1. in 451 | if at_bottom 452 | then model 453 | else aux model 454 | in 455 | aux model 456 | 457 | | Noop -> model 458 | ), 459 | Cmd.none 460 | 461 | (*****************************************************************************) 462 | (* Entry point *) 463 | (*****************************************************************************) 464 | 465 | let app = 466 | { Playground. 467 | view; 468 | update; 469 | init = (fun () -> initial_model, Cmd.none); 470 | subscriptions = (fun _ -> Sub.batch [ 471 | Sub.on_animation_frame (fun x -> Tick x); 472 | Sub.on_key_down (fun key -> msg_of_key key); 473 | ]); 474 | } 475 | 476 | let main = 477 | Playground_platform.run_app app 478 | -------------------------------------------------------------------------------- /games/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names 3 | Snake 4 | Pong 5 | Tetris 6 | Asteroid 7 | ) 8 | (libraries 9 | elm_playground 10 | elm_playground_native ; implem of virtual elm_playground 11 | ) 12 | ) 13 | -------------------------------------------------------------------------------- /games/template.ml: -------------------------------------------------------------------------------- 1 | open Playground 2 | 3 | (*****************************************************************************) 4 | (* Prelude *) 5 | (*****************************************************************************) 6 | (* Port of the Pong clone XXX, 7 | * but using OCaml instead of Elm, and using Playground instead of YYY. 8 | *) 9 | 10 | (*****************************************************************************) 11 | (* Model *) 12 | (*****************************************************************************) 13 | 14 | let initial_model = () 15 | 16 | (*****************************************************************************) 17 | (* View *) 18 | (*****************************************************************************) 19 | 20 | let view _computer _model = [] 21 | 22 | (*****************************************************************************) 23 | (* Update *) 24 | (*****************************************************************************) 25 | let update _msg model = 26 | model 27 | 28 | (*****************************************************************************) 29 | (* Entry point *) 30 | (*****************************************************************************) 31 | 32 | let app = 33 | game view update initial_model 34 | 35 | let main = 36 | Playground_platform.run_app app 37 | -------------------------------------------------------------------------------- /games_js/Asteroid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /games_js/Asteroid.ml: -------------------------------------------------------------------------------- 1 | ../games/Asteroid.ml -------------------------------------------------------------------------------- /games_js/Pong.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /games_js/Pong.ml: -------------------------------------------------------------------------------- 1 | ../games/Pong.ml -------------------------------------------------------------------------------- /games_js/Snake.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /games_js/Snake.ml: -------------------------------------------------------------------------------- 1 | ../games/Snake.ml -------------------------------------------------------------------------------- /games_js/Template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /games_js/Tetris.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /games_js/Tetris.ml: -------------------------------------------------------------------------------- 1 | ../games/Tetris.ml -------------------------------------------------------------------------------- /games_js/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names 3 | Snake 4 | Pong 5 | Tetris 6 | Asteroid 7 | ) 8 | (libraries 9 | unix 10 | elm_playground 11 | elm_playground_web ; implem of virtual elm_playground 12 | ) 13 | (modes js) 14 | (link_flags -no-check-prims) 15 | ) 16 | -------------------------------------------------------------------------------- /playground/Playground_platform.mli: -------------------------------------------------------------------------------- 1 | val run_app: 2 | ('a, 'b) Playground.app -> unit 3 | -------------------------------------------------------------------------------- /playground/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name elm_playground) 3 | (virtual_modules Playground_platform) 4 | (wrapped false) 5 | (libraries 6 | ; commons 7 | elm_core elm_system 8 | ) 9 | ; (preprocess (pps profiling.ppx)) 10 | ) 11 | 12 | (documentation 13 | (package elm_playground) 14 | (mld_files index)) 15 | -------------------------------------------------------------------------------- /playground/index.mld: -------------------------------------------------------------------------------- 1 | {0:top elm_playground} 2 | 3 | {1 OCaml Elm Playground } 4 | 5 | Create pictures, animations, and games with OCaml! 6 | 7 | This is a port of the excellent {{:https://github.com/evancz/elm-playground} Elm playground package} to OCaml. 8 | 9 | This is the package I wanted when I was learning programming. Start by 10 | putting shapes on screen and work up to making games. I hope this 11 | package will be fun for a broad range of ages and backgrounds! 12 | 13 | {2 Pictures} 14 | 15 | A picture is a list of shapes. For example, this picture combines a brown rectangle and a green circle to make a tree 16 | {[ 17 | open Playground 18 | 19 | let app = 20 | picture [ 21 | rectangle brown 40. 200.; 22 | circle green 100. 23 | |> move_up 100.; 24 | ] 25 | 26 | let main = Playground_platform.run_app app 27 | ]} 28 | 29 | Play around to get familiar with all the different {{!Playground.shapes} shapes} 30 | and {{!Playground.transformations} transformations} in the library. 31 | 32 | {2 Animations} 33 | 34 | An animation is a list of shapes that changes over time. For example, 35 | here is a spinning triangle: 36 | 37 | {[ 38 | open Playground 39 | 40 | let view time = [ 41 | triangle orange 50. 42 | |> rotate (spin 8. time); 43 | ] 44 | 45 | let app = animation view 46 | 47 | let main = Playground_platform.run_app app 48 | ]} 49 | 50 | It will do a full spin every 8 seconds. 51 | 52 | Maybe try making a car with spinning octogons as wheels? Try using 53 | {!Playground.wave} to move things back-and-forth? Try using 54 | {!Playground.zigzag} to fade things in-and-out? 55 | 56 | {2 Games} 57 | 58 | A game lets you use input from the mouse and keyboard to change your 59 | picture. For example, here is a square that moves around based on the 60 | arrow keys: 61 | 62 | {[ 63 | open Playground 64 | 65 | let view _computer (x, y) = [ 66 | square blue 40. 67 | |> move x y 68 | ] 69 | 70 | let update computer (x, y) = 71 | (x +. to_x computer.keyboard, y +. to_y computer.keyboard) 72 | 73 | let app = 74 | game view update (0., 0.) 75 | 76 | let main = Playground_platform.run_app app 77 | ]} 78 | 79 | Every game has three important parts: 80 | + [memory] - Store information. Our example stores [(x,y)] coordinates. 81 | + [update] - Update the memory based on mouse movements, key presses, etc. Our example moves the [(x,y)] coordinate around based on the arrow keys. 82 | + [view] - Turn the memory into a picture. Our example just shows one blue square at the [(x,y)] coordinate we have been tracking in memory. 83 | 84 | 85 | When you start making fancier games, you will store fancier things in 86 | memory. There is a lot of room to develop your programming skills 87 | here: Making lists, using records, creating custom types, etc. 88 | 89 | I started off trying to make Pong, then worked on games like Breakout 90 | and Space Invaders as I learned more and more. It was really fun, and 91 | I hope it will be for you as well! 92 | 93 | 94 | {2:index Index of modules} 95 | 96 | {!modules: 97 | Playground 98 | Playground_platform 99 | } 100 | -------------------------------------------------------------------------------- /playground/native/Playground_platform.ml: -------------------------------------------------------------------------------- 1 | open Basics 2 | open Color 3 | module E = Sub 4 | open Tsdl 5 | 6 | (*****************************************************************************) 7 | (* Prelude *) 8 | (*****************************************************************************) 9 | (* Native backend of Playground using Cairo and SDL. 10 | * 11 | * history: 12 | * - use Graphics, but no keydown/keyup 13 | * - use ocaml-SDL, but initialy lack example to work with Cairo 14 | * - use TSDL+cairo 15 | *) 16 | 17 | (*****************************************************************************) 18 | (* Helpers *) 19 | (*****************************************************************************) 20 | (* was in my Commom.ml before, could move in a core/Regexp_.ml *) 21 | 22 | let spf = Printf.sprintf 23 | 24 | let ( =~ ) s re = 25 | Str.string_match (Str.regexp re) s 0 26 | let matched (i: int) (s: string) : string = Str.matched_group i s 27 | let _matched1 s = matched 1 s 28 | let _matched2 s = (matched 1 s, matched 2 s) 29 | let matched3 s = (matched 1 s, matched 2 s, matched 3 s) 30 | 31 | (*****************************************************************************) 32 | (* Image loading (independent of Playground) *) 33 | (*****************************************************************************) 34 | 35 | (* from ocurl/examples/opar.ml *) 36 | let writer _fname _conn accum data = 37 | (* show_progress fname conn; *) 38 | Buffer.add_string accum data; 39 | String.length data 40 | 41 | let save fname content = 42 | let fp = open_out_bin fname in 43 | Buffer.output_buffer fp content; 44 | close_out fp 45 | 46 | let curl_url fname url = 47 | let result = Buffer.create 16384 in 48 | let conn = Curl.init () in 49 | Curl.set_writefunction conn (writer fname conn result); 50 | Curl.set_followlocation conn true; 51 | Curl.set_url conn url; 52 | Curl.perform conn; 53 | Curl.cleanup conn; 54 | save fname result 55 | 56 | 57 | let png_file_of_url url = 58 | let image = 59 | (* copy of ImageLib_unix.openfile url but not falling back for GIF to 60 | * convert, which does not work well on my mac at least *) 61 | let ext = ImageUtil_unix.get_extension' url in 62 | let fn = Filename.temp_file "imagelib1" ("." ^ ext) in 63 | curl_url fn url; 64 | let ich = ImageUtil_unix.chunk_reader_of_path fn in 65 | let extension = ImageUtil_unix.get_extension' fn in 66 | Logs.debug (fun m -> m "reading png_file_of_url"); 67 | try ImageLib.openfile ~extension ich 68 | with Image.Not_yet_implemented _ -> failwith (Printf.sprintf "PB with %s" fn) 69 | in 70 | let tmpfile = 71 | Filename.temp_file "imagelib2" ".png" 72 | (* "/tmp/imagelib.png" *) 73 | in 74 | Logs.debug (fun m -> m "saving png_file_of_url"); 75 | ImageLib_unix.writefile tmpfile image; 76 | tmpfile 77 | 78 | (*****************************************************************************) 79 | (* Render (independent of Playground) *) 80 | (*****************************************************************************) 81 | 82 | let (g_cr: Cairo.context option ref) = ref None 83 | let get_cr () = 84 | match !g_cr with 85 | | None -> failwith "no cr" 86 | | Some x -> x 87 | 88 | let g_sx = ref 0 89 | let g_sy = ref 0 90 | 91 | (* for external images: url -> surface via Cairo.PNG.create *) 92 | let himages = Hashtbl.create 101 93 | 94 | (* less: save_excursion? *) 95 | let with_cr f = 96 | let cr = get_cr () in 97 | Cairo.save cr; 98 | let res = f cr in 99 | Cairo.restore cr; 100 | res 101 | 102 | let set_color cr color alpha = 103 | let (r,g,b) = 104 | match color with 105 | | Rgb (r,g,b) -> float r / 255., float g / 255., float b / 255. 106 | | Hex s -> 107 | let s = String.lowercase_ascii s in 108 | if s =~ "^#\\([a-f0-9][a-f0-9]\\)\\([a-f0-9][a-f0-9]\\)\\([a-f0-9][a-f0-9]\\)$" 109 | then 110 | let (a, b, c) = matched3 s in 111 | let f x = 112 | (("0x" ^ x) |> int_of_string |> float) / 255. 113 | in 114 | f a, f b, f c 115 | else failwith (spf "wrong color format: %s" s) 116 | in 117 | Cairo.set_source_rgba cr r g b (clamp 0. 1. alpha) 118 | 119 | let debug_coordinates cr = 120 | let sx = !g_sx in 121 | let sy = !g_sy in 122 | let (x0,y0) = Cairo.device_to_user cr 0. 0. in 123 | let (xmax, ymax) = Cairo.device_to_user cr (float sx) (float sy) in 124 | Logs.debug (fun m -> m "device 0,0 => %.1f %.1f, device %d,%d => %.1f %.1f" 125 | x0 y0 sx sy xmax ymax) 126 | 127 | (* Cairo (0,0) is at the top left of the screen, in which as y goes up, 128 | * the coordinates are down on the physical screen. Elm uses a better 129 | * default where if your y goes up, then it's upper on the screen. 130 | * Here we convert the Elm coordinate system to Cairo. Note that 131 | * it's hard to use one of the rotate/translate Cairo function to emulate 132 | * that as only y need to change (maybe need to create a special matrix?) 133 | *) 134 | let convert (x, y) = 135 | x, -. y 136 | 137 | 138 | let render_transform cr x y angle s = 139 | Cairo.translate cr x y; 140 | Cairo.rotate cr (-. (Basics.degrees_to_radians angle)); 141 | Cairo.scale cr s s; 142 | () 143 | 144 | 145 | let rec ngon_points cr i n radius = 146 | if i == n 147 | then () 148 | else begin 149 | let a = turns (float i / float n - 0.25) in 150 | let x = radius * cos a in 151 | let y = radius * sin a in 152 | (if i = 0 153 | then Cairo.move_to cr x y 154 | else Cairo.line_to cr x y 155 | ); 156 | ngon_points cr (Stdlib.(+) i 1) n radius 157 | end 158 | 159 | let render_ngon hook color n radius x y angle s alpha = 160 | (*pr2_gen (x,y,n,radius);*) 161 | let (x, y) = convert (x, y) in 162 | 163 | with_cr (fun cr -> 164 | hook cr; 165 | set_color cr color alpha; 166 | render_transform cr x y angle s; 167 | 168 | ngon_points cr 0 n radius; 169 | Cairo.fill cr; 170 | ) 171 | 172 | 173 | let render_oval hook color w h x y _angle _s alpha = 174 | (*pr2_gen (x,y,w,h);*) 175 | 176 | let x = x - (w / 2.) in 177 | let y = y + (h / 2.) in 178 | let (x,y) = convert (x,y) in 179 | 180 | with_cr (fun cr -> 181 | hook cr; 182 | set_color cr color alpha; 183 | 184 | (* code in cairo.mli to draw ellipsis *) 185 | Cairo.translate cr (x +. w /. 2.) (y +. h /. 2.); 186 | Cairo.scale cr (w /. 2.) (h /. 2.); 187 | 188 | Cairo.arc cr 0. 0. 1. 0. pi2; 189 | Cairo.fill cr; 190 | ) 191 | 192 | 193 | let render_circle hook color radius x y angle s alpha = 194 | (*pr2_gen (x,y, radius);*) 195 | let (x,y) = convert (x,y) in 196 | 197 | with_cr (fun cr -> 198 | hook cr; 199 | set_color cr color alpha; 200 | render_transform cr x y angle s; 201 | 202 | Cairo.arc cr 0. 0. radius 0. pi2; 203 | Cairo.fill cr; 204 | ) 205 | 206 | let render_polygon hook color points x y angle s alpha = 207 | let (x,y) = convert (x,y) in 208 | 209 | with_cr (fun cr -> 210 | hook cr; 211 | set_color cr color alpha; 212 | render_transform cr x y angle s; 213 | 214 | (match points with 215 | | [] -> failwith "not enough points in polygon" 216 | | (x, y)::xs -> 217 | let (x,y) = convert (x,y) in 218 | Cairo.move_to cr x y; 219 | xs |> List.iter (fun (x, y) -> 220 | let (x,y) = convert (x,y) in 221 | Cairo.line_to cr x y 222 | ); 223 | Cairo.line_to cr x y; 224 | Cairo.fill cr; 225 | ) 226 | ) 227 | 228 | 229 | let render_rectangle hook color w h x y angle s alpha = 230 | render_polygon hook color 231 | [ (-. w / 2., h /. 2.); 232 | ( w / 2., h /. 2.); 233 | ( w / 2., -.h /. 2.); 234 | (-. w / 2., -.h /. 2.); 235 | ] x y angle s alpha 236 | 237 | let render_words hook color str x y angle s alpha = 238 | let (x,y) = convert (x,y) in 239 | 240 | with_cr (fun cr -> 241 | hook cr; 242 | set_color cr color alpha; 243 | render_transform cr x y angle s; 244 | 245 | let extent = Cairo.text_extents cr str in 246 | let tw = extent.Cairo.width in 247 | let th = extent.Cairo.height in 248 | 249 | Cairo.move_to cr (-. tw / 2.) (th / 2.); 250 | Cairo.show_text cr str; 251 | ) 252 | 253 | let render_image hook w h src x y angle s _alpha = 254 | let (x,y) = convert (x,y) in 255 | 256 | let surface = 257 | try 258 | Hashtbl.find himages src 259 | with Not_found -> 260 | (* pr2_gen src; *) 261 | let file = png_file_of_url src in 262 | let surface = Cairo.PNG.create file in 263 | Hashtbl.add himages src surface; 264 | surface 265 | in 266 | 267 | with_cr (fun cr -> 268 | hook cr; 269 | render_transform cr x y angle s; 270 | 271 | Cairo.set_source_surface cr surface ~x:(-. w / 2.) ~y:(-. h / 2.); 272 | Cairo.paint cr; 273 | ) 274 | 275 | (*****************************************************************************) 276 | (* Render playground *) 277 | (*****************************************************************************) 278 | open Playground 279 | 280 | (* ugly, to handle Group *) 281 | type hook = Cairo.context -> unit 282 | let empty_hook = (fun _cr -> ()) 283 | 284 | let rec (render_shape: hook -> shape -> unit) = 285 | fun hook { x; y; angle; scale; alpha; form} -> 286 | match form with 287 | | Circle (color, radius) -> 288 | render_circle hook color radius x y angle scale alpha 289 | | Oval (color, width, height) -> 290 | render_oval hook color width height x y angle scale alpha 291 | | Rectangle (color, width, height) -> 292 | render_rectangle hook color width height x y angle scale alpha 293 | | Ngon (color, n, radius) -> 294 | render_ngon hook color n radius x y angle scale alpha 295 | | Polygon (color, points) -> 296 | render_polygon hook color points x y angle scale alpha 297 | | Words (color, str) -> 298 | render_words hook color str x y angle scale alpha 299 | | Image (w, h, src) -> 300 | render_image hook w h src x y angle scale alpha 301 | | Group xs -> 302 | (* TODO: alpha *) 303 | let hook = (fun cr -> 304 | hook cr; 305 | let (x, y) = convert (x, y) in 306 | render_transform cr x y angle scale 307 | ) in 308 | List.iter (render_shape hook) xs 309 | 310 | let (render: shape list -> unit) = fun shapes -> 311 | List.iter (render_shape empty_hook) shapes 312 | 313 | 314 | (*****************************************************************************) 315 | (* FPS (using Cairo) *) 316 | (*****************************************************************************) 317 | 318 | module Fps = struct 319 | (* was in cairo/examples/graphics_demo.ml *) 320 | let lastfps = ref (Unix.gettimeofday ()) 321 | let frames = ref 0 322 | let fps = ref 0. 323 | 324 | let update_fps () = 325 | let t = Unix.gettimeofday () in 326 | let dt = t -. !lastfps in 327 | if dt > 0.5 then ( 328 | fps := float !frames /. dt; 329 | frames := 0; 330 | lastfps := t 331 | ); 332 | incr frames 333 | 334 | let draw_fps cr width height = 335 | Cairo.set_source_rgba cr 0. 0. 0. 1.; 336 | Cairo.move_to cr (0.05 *. width) (0.95 *. height); 337 | Cairo.show_text cr (Printf.sprintf "%gx%g -- %.0f fps" width height !fps) 338 | end 339 | 340 | (*****************************************************************************) 341 | (* Run app *) 342 | (*****************************************************************************) 343 | 344 | (* The tsdl library is a heavy user of Result, which is annoying 345 | * to check at every calls; fortunately OCaml 4.08 allow to define 346 | * monadic operators to remove some boilerplate! 347 | *) 348 | let (let*) o f = 349 | match o with 350 | | Error (`Msg msg) -> 351 | failwith (spf "TSDL error: %s" msg) 352 | | Ok x -> f x 353 | 354 | let scancode_to_keystring = function 355 | | "Left" -> "ArrowLeft" 356 | | "Right" -> "ArrowRight" 357 | | "Up" -> "ArrowUp" 358 | | "Down" -> "ArrowDown" 359 | 360 | | "Q" -> exit 0 361 | | s -> String.lowercase_ascii s 362 | 363 | let run_app app = 364 | let sx = int_of_float Playground.default_width in 365 | let sy = int_of_float Playground.default_height in 366 | 367 | let* () = Sdl.init Sdl.Init.(video + events) in 368 | let* sdl_window = Sdl.create_window ~w:sx ~h:sy "Playground using SDL+Cairo" 369 | Sdl.Window.shown in 370 | let event = Sdl.Event.create () in 371 | 372 | let* window_surface = Sdl.get_window_surface sdl_window in 373 | 374 | let pixels = Sdl.get_surface_pixels window_surface Bigarray.int32 in 375 | assert (Bigarray.Array1.dim pixels = sx *.. sy); 376 | 377 | (* less? need that? *) 378 | Bigarray.Array1.fill pixels 0xFFFFFFFFl ; 379 | let pixels = 380 | try 381 | let genarray = Bigarray.genarray_of_array1 pixels in 382 | Bigarray.reshape_2 genarray sy sx 383 | with _ -> 384 | let len = Bigarray.Array1.dim pixels in 385 | failwith (spf 386 | "Error while reshaping pixel array of length %d to screen size %d x %d" 387 | len sx sy) 388 | in 389 | (* Create a Cairo surface to write on the pixels *) 390 | let sdl_surface = 391 | Cairo.Image.create_for_data32 ~w:sx ~h:sy pixels 392 | in 393 | let cr = Cairo.create sdl_surface in 394 | 395 | Cairo.identity_matrix cr; 396 | g_cr := Some cr; 397 | g_sx := sx; 398 | g_sy := sy; 399 | debug_coordinates cr; 400 | 401 | let initmodel, _cmdsTODO = app.Playground.init () in 402 | let model = ref initmodel in 403 | 404 | (* typing "Q" will cause an 'exit 0' that will exit the loop *) 405 | while true do 406 | Cairo.save cr; 407 | 408 | (* reset the surface content *) 409 | Cairo.set_source_rgba cr 1. 1. 1. 1.; 410 | Cairo.paint cr; 411 | 412 | (* elm-convetion: set the origin (0, 0) in the center of the surface *) 413 | Cairo.identity_matrix cr; 414 | Cairo.translate cr (float sx / 2.) (float sy / 2.); 415 | (*debug_coordinates cr;*) 416 | 417 | (* one frame *) 418 | let time = Unix.gettimeofday() in 419 | 420 | let subs = app.Playground.subscriptions !model in 421 | 422 | let event = 423 | if Sdl.poll_event (Some event) 424 | then 425 | let event_type = Sdl.Event.get event Sdl.Event.typ in 426 | (match event_type with 427 | | x when x = Sdl.Event.mouse_motion -> 428 | let x = Sdl.Event.(get event mouse_motion_x) in 429 | let y = Sdl.Event.(get event mouse_motion_y) in 430 | let (x, y) = Cairo.device_to_user cr (float x) (float y) in 431 | let (x, y) = convert (x, y) in 432 | E.EMouseMove (int_of_float x, int_of_float y) 433 | 434 | | x when x = Sdl.Event.mouse_button_down -> 435 | E.EMouseButton (true) 436 | 437 | | x when x = Sdl.Event.mouse_button_up -> 438 | E.EMouseButton (false) 439 | 440 | | x when x = Sdl.Event.key_down -> 441 | let key = Sdl.(get_key_name Event.(get event keyboard_keycode)) in 442 | let str = scancode_to_keystring key in 443 | E.EKeyChanged (true, str) 444 | 445 | | x when x = Sdl.Event.key_up -> 446 | let key = Sdl.(get_key_name Event.(get event keyboard_keycode)) in 447 | let str = scancode_to_keystring key in 448 | E.EKeyChanged (false, str) 449 | 450 | (* default case *) 451 | | _ -> E.ETick time 452 | ) 453 | else E.ETick time 454 | in 455 | 456 | let msg_opt = E.event_to_msgopt event subs in 457 | (match msg_opt with 458 | | None -> () 459 | | Some msg -> 460 | let newmodel, _cmds = app.Playground.update msg !model in 461 | model := newmodel; 462 | ); 463 | 464 | let shapes = app.Playground.view !model in 465 | render shapes; 466 | 467 | Cairo.restore cr; 468 | Fps.draw_fps cr (float sx) (float sy); 469 | 470 | (* Don't forget to flush the surface before using its content. *) 471 | Cairo.Surface.flush sdl_surface; 472 | let* () = Sdl.update_window_surface sdl_window in 473 | 474 | (* Update our fps counter. *) 475 | Fps.update_fps (); 476 | done 477 | -------------------------------------------------------------------------------- /playground/native/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name elm_playground_native) 3 | (implements elm_playground) 4 | ; problems with tsdl on windows I think 5 | (libraries 6 | str 7 | logs 8 | cairo2 tsdl 9 | imagelib.unix curl 10 | 11 | elm_playground 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /playground/web/Playground_platform.ml: -------------------------------------------------------------------------------- 1 | open Basics 2 | open Playground 3 | open Color 4 | module E = Sub 5 | 6 | open Js_browser 7 | 8 | (*****************************************************************************) 9 | (* Prelude *) 10 | (*****************************************************************************) 11 | (* Web backend of Playground. 12 | * 13 | * TODO: switch to Canvas, use CanvasToCairo? so closer to native playground? 14 | * I originally used SVG because that's what elm-playground is using, but 15 | * I have pbs with the mouse coordinate and the bounding box conversion, 16 | * so maybe simpler to switch to Canvas. 17 | *) 18 | 19 | (* When set to true, we generate a new frame only when there is an event. 20 | * It makes things easier to observe with Chrome developer tools. 21 | *) 22 | let debug = ref false 23 | 24 | (*****************************************************************************) 25 | (* Helpers modules *) 26 | (*****************************************************************************) 27 | 28 | (* can also use Printf.printf I think *) 29 | let log s = 30 | Js_browser.Console.log Js_browser.console (Ojs.string_to_js s) 31 | 32 | let spf = Printf.sprintf 33 | 34 | let string_of_number x = 35 | spf "%f" x 36 | 37 | (* when using the VDROM, but tedious to use request_animation_frame 38 | * and a global keyboard handler => switch to basic Dom. 39 | *) 40 | (* 41 | module V = Vdom 42 | *) 43 | 44 | (* when using directly the DOM *) 45 | module V = struct 46 | type t = Element.t 47 | 48 | type 'a vdom = t 49 | 50 | type attr = 51 | | Attr of string * string 52 | | Style of string * string 53 | 54 | let svg_ns = "http://www.w3.org/2000/svg" 55 | 56 | let svg_elt tag ~a children = 57 | (* bugfix: for svg elt we need to pass the ns! (namespace_URI), otherwise 58 | * it will not render anything. 59 | *) 60 | let elt = Document.create_element_ns document svg_ns tag in 61 | a |> List.iter (function 62 | | Attr (k, v) -> 63 | Element.set_attribute elt k v 64 | | Style (k, v) -> 65 | Ojs.set_prop_ascii 66 | (Ojs.get_prop_ascii (Element.t_to_js elt) "style") 67 | k 68 | (Ojs.string_to_js v) 69 | ); 70 | children |> List.iter (fun child -> 71 | Element.append_child elt child 72 | ); 73 | elt 74 | let style s1 s2 = 75 | Style (s1, s2) 76 | let attr s v = 77 | Attr (s, v) 78 | end 79 | 80 | module Html = struct 81 | let style = V.style 82 | end 83 | 84 | module Svg = struct 85 | type 'msg t = 'msg V.vdom 86 | 87 | let svg attrs xs = 88 | V.svg_elt "svg" ~a:attrs xs 89 | 90 | let trusted_node s attrs xs = 91 | V.svg_elt s ~a:attrs xs 92 | 93 | (* !subtle! need to eta-expand. You can't factorize with 94 | * let circle = trusted_node "circle" otherwise 95 | * we don't get the general 'msg vdom type inferred but 96 | * the first call to circle in this file will bind forever the type 97 | * parameter (e.g., to `Resize of int * int). 98 | * You can see the wrongly inferred type by using ocamlc -i on 99 | * this file (you may need dune --verbose to get the full list of -I first). 100 | *) 101 | let circle a b = 102 | trusted_node "circle" a b 103 | let ellipse a b = 104 | trusted_node "ellipse" a b 105 | let rect a b = 106 | trusted_node "rect" a b 107 | let polygon a b = 108 | trusted_node "polygon" a b 109 | let image a b = 110 | trusted_node "image" a b 111 | 112 | module Attributes = struct 113 | let viewBox = V.attr "viewBox" 114 | 115 | let width = V.attr "width" 116 | let height = V.attr "height" 117 | 118 | let r = V.attr "r" 119 | let rx = V.attr "rx" 120 | let ry = V.attr "ry" 121 | let fill = V.attr "fill" 122 | let points = V.attr "points" 123 | let transform = V.attr "transform" 124 | let opacity = V.attr "opacity" 125 | 126 | let href = V.attr "href" 127 | 128 | end 129 | end 130 | 131 | (*****************************************************************************) 132 | (* Render *) 133 | (*****************************************************************************) 134 | 135 | let render_color color = 136 | match color with 137 | | Hex str -> str 138 | | Rgb (r,g,b) -> Printf.sprintf "rgb(%d,%d,%d)" r g b 139 | 140 | let render_transform x y a s = 141 | if a = 0. then 142 | if s = 1. 143 | then 144 | spf "translate(%s, %s)" 145 | (string_of_number x) (string_of_number (-. y)) 146 | else 147 | spf "translate(%s, %s) scale(%s)" 148 | (string_of_number x) (string_of_number (-. y)) 149 | (string_of_number s) 150 | else 151 | if s = 1. 152 | then 153 | spf "translate(%s, %s) rotate(%s)" 154 | (string_of_number x) (string_of_number (-. y)) 155 | (string_of_number (-. a)) 156 | else 157 | spf "translate(%s, %s) rotate(%s) scale(%s) " 158 | (string_of_number x) (string_of_number (-. y)) 159 | (string_of_number (-. a)) 160 | (string_of_number s) 161 | 162 | let render_rect_transform width height x y angle s = 163 | render_transform x y angle s ^ 164 | spf " translate(%s, %s)" 165 | (string_of_number (-. width / 2.)) 166 | (string_of_number (-. height / 2.)) 167 | 168 | 169 | let render_alpha alpha = 170 | if alpha = 1. 171 | then [] 172 | else [Svg.Attributes.opacity (string_of_number (clamp 0. 1. alpha))] 173 | 174 | let render_circle color radius x y angle s alpha = 175 | Svg.circle 176 | (Svg.Attributes.r (string_of_number radius) :: 177 | Svg.Attributes.fill (render_color color) :: 178 | Svg.Attributes.transform (render_transform x y angle s):: 179 | render_alpha alpha 180 | ) 181 | [] 182 | 183 | let render_oval color width height x y angle s alpha = 184 | Svg.ellipse 185 | (Svg.Attributes.rx (string_of_number (width / 2.)) :: 186 | Svg.Attributes.ry (string_of_number (height / 2.)) :: 187 | Svg.Attributes.fill (render_color color) :: 188 | Svg.Attributes.transform (render_transform x y angle s):: 189 | render_alpha alpha 190 | ) 191 | [] 192 | 193 | let render_rectangle color w h x y angle s alpha = 194 | Svg.rect 195 | (Svg.Attributes.width (string_of_number (w)) :: 196 | Svg.Attributes.height (string_of_number (h)) :: 197 | Svg.Attributes.fill (render_color color) :: 198 | Svg.Attributes.transform (render_rect_transform w h x y angle s):: 199 | render_alpha alpha 200 | ) 201 | [] 202 | 203 | let rec to_ngon_points i n radius str = 204 | if i == n 205 | then str 206 | else 207 | let a = turns (float_of_int i / float_of_int n - 0.25) in 208 | let x = radius * cos a in 209 | let y = radius * sin a in 210 | to_ngon_points (Stdlib.(+) i 1) n radius 211 | (spf "%s%s,%s " str (string_of_number x) (string_of_number y)) 212 | 213 | let render_ngon color n radius x y angle s alpha = 214 | Svg.polygon 215 | (Svg.Attributes.points (to_ngon_points 0 n radius "") :: 216 | Svg.Attributes.fill (render_color color) :: 217 | Svg.Attributes.transform (render_transform x y angle s):: 218 | render_alpha alpha 219 | ) 220 | [] 221 | 222 | (* TODO *) 223 | let render_words color _str x y angle s alpha = 224 | Svg.circle 225 | (Svg.Attributes.r (string_of_number 10.) :: 226 | Svg.Attributes.fill (render_color color) :: 227 | Svg.Attributes.transform (render_transform x y angle s):: 228 | render_alpha alpha 229 | ) 230 | [] 231 | 232 | (* TODO *) 233 | let render_polygon color _points x y angle s alpha = 234 | Svg.circle 235 | (Svg.Attributes.r (string_of_number 10.) :: 236 | Svg.Attributes.fill (render_color color) :: 237 | Svg.Attributes.transform (render_transform x y angle s):: 238 | render_alpha alpha 239 | ) 240 | [] 241 | 242 | let render_image w h src x y angle s alpha = 243 | Svg.image 244 | (Svg.Attributes.href src:: (* was xlinkHref but require attributeNS *) 245 | Svg.Attributes.width (string_of_number w):: 246 | Svg.Attributes.width (string_of_number h):: 247 | Svg.Attributes.fill (render_color yellow) :: 248 | Svg.Attributes.transform (render_rect_transform w h x y angle s):: 249 | render_alpha alpha 250 | ) 251 | [] 252 | 253 | 254 | let (render_shape: shape -> 'msg Svg.t) = 255 | fun { x; y; angle; scale; alpha; form} -> 256 | match form with 257 | | Circle (color, radius) -> 258 | render_circle color radius x y angle scale alpha 259 | | Oval (color, width, height) -> 260 | render_oval color width height x y angle scale alpha 261 | | Rectangle (color, width, height) -> 262 | render_rectangle color width height x y angle scale alpha 263 | | Ngon (color, n, radius) -> 264 | render_ngon color n radius x y angle scale alpha 265 | | Polygon (color, points) -> 266 | render_polygon color points x y angle scale alpha 267 | | Words (color, str) -> 268 | render_words color str x y angle scale alpha 269 | | Image (w, h, src) -> 270 | render_image w h src x y angle scale alpha 271 | | Group _ -> 272 | failwith "Todo" 273 | 274 | 275 | let (render: screen -> shape list -> 'msg Svg.t) = fun screen shapes -> 276 | let w = screen.width |> string_of_number in 277 | let h = screen.height |> string_of_number in 278 | let x = screen.left |> string_of_number in 279 | let y = screen.bottom |> string_of_number in 280 | 281 | Svg.svg 282 | [Svg.Attributes.viewBox (x ^ " " ^ y ^ " " ^ w ^ " " ^ h); 283 | Html.style "position" "fixed"; 284 | Html.style "top" "0"; 285 | Html.style "left" "0"; 286 | Svg.Attributes.width "100%"; 287 | Svg.Attributes.height "100%"; 288 | ] 289 | (List.map render_shape shapes) 290 | 291 | (*****************************************************************************) 292 | (* Event management *) 293 | (*****************************************************************************) 294 | 295 | let adjust_x_y x y dim screen = 296 | log (spf "%f %f" x y); 297 | log (spf "h=%f, w=%f, left=%f, right=%f, top=%f, bottom=%f" 298 | (Rect.height dim) (Rect.width dim) (Rect.left dim) (Rect.right dim) 299 | (Rect.top dim) (Rect.bottom dim)); 300 | 301 | let x = x - Rect.left dim in 302 | let x = x * screen.width / Rect.width dim in 303 | let x = screen.left + x in 304 | 305 | let y = y - Rect.top dim in 306 | let y = y * screen.height / Rect.height dim in 307 | let y = screen.top - y in 308 | 309 | x, y 310 | 311 | let adjust_key key = 312 | log (spf "key = '%s'" key); 313 | match key with 314 | | " " -> "space" 315 | | _ -> key 316 | 317 | let js_event_to_event evt screen = 318 | let ty = Event.type_ evt in 319 | match ty with 320 | | "mousemove" -> 321 | let (x, y) = Event.page_x evt, Event.page_y evt in 322 | 323 | let o = Event.target evt in 324 | let elt = Element.t_of_js o in 325 | let dim = Element.get_bounding_client_rect elt in 326 | 327 | let x, y = adjust_x_y x y dim screen in 328 | Some (E.EMouseMove (int_of_float x, int_of_float y)) 329 | | "mousedown" | "mouseup" -> 330 | let b = Event.buttons evt > 0 in 331 | Some (E.EMouseButton b) 332 | 333 | | "keydown" -> 334 | let key = Event.key evt in 335 | let key = adjust_key key in 336 | Some (E.EKeyChanged (true, key)) 337 | | "keyup" -> 338 | let key = Event.key evt in 339 | let key = adjust_key key in 340 | Some (E.EKeyChanged (false, key)) 341 | 342 | | _ -> None 343 | 344 | (*****************************************************************************) 345 | (* run_app *) 346 | (*****************************************************************************) 347 | 348 | (* when using Vdom: 349 | let (vdom_app_of_app: ('model, 'msg) Playground.app -> ('model, 'msg) V.app) = 350 | fun { Playground. init; view; update; subscriptions = _subTODO } -> 351 | V.app 352 | ~init:( 353 | let (model, _cmdsTODO) = init () in 354 | model, V.Cmd.Batch []) 355 | ~view:(fun model -> 356 | (* TODO: can change! *) 357 | let screen = Playground.to_screen 600. 600. in 358 | let shapes = view model in 359 | render screen shapes 360 | ) 361 | ~update:(fun model msg -> 362 | let model, _cmds = update msg model in 363 | model, V.Cmd.Batch [] 364 | ) 365 | () 366 | let run_app app = 367 | let app = vdom_app_of_app app in 368 | let run () = 369 | Vdom_blit.run app 370 | |> Vdom_blit.dom 371 | |> Element.append_child (Document.body document) in 372 | let () = Window.set_onload window run in 373 | () 374 | *) 375 | 376 | (* when using the simple DOM *) 377 | let run_app app = 378 | Window.set_onload window (fun () -> 379 | 380 | let sx = Playground.default_width in 381 | let sy = Playground.default_height in 382 | let screen = Playground.to_screen sx sy in 383 | 384 | let (initmodel, _cmdsTODO) = app.Playground.init () in 385 | let model = ref initmodel in 386 | 387 | let process_playground_event event = 388 | let subs = app.Playground.subscriptions !model in 389 | let msg_opt = E.event_to_msgopt event subs in 390 | (match msg_opt with 391 | | None -> () 392 | | Some msg -> 393 | let newmodel, _cmds = app.Playground.update msg !model in 394 | model := newmodel; 395 | ); 396 | in 397 | 398 | (* one frame *) 399 | let rec animation_frame time = 400 | let time = time /. 1000. in 401 | (* log (spf "time: %f" time); *) 402 | let event = 403 | E.ETick time 404 | in 405 | process_playground_event event; 406 | 407 | let shapes = app.Playground.view !model in 408 | let elt = render screen shapes in 409 | 410 | let body = Document.body document in 411 | Element.remove_all_children body; 412 | 413 | Element.append_child body elt; 414 | 415 | if not !debug 416 | then Window.request_animation_frame window animation_frame; 417 | in 418 | Window.request_animation_frame window animation_frame; 419 | 420 | let on_js_event evt = 421 | let evt_opt = js_event_to_event evt screen in 422 | (match evt_opt with 423 | | None -> () 424 | | Some event -> process_playground_event event 425 | ); 426 | if !debug then Window.request_animation_frame window animation_frame; 427 | in 428 | [ 429 | Event.Mousemove; 430 | Event.Mousedown; 431 | Event.Mouseup; 432 | Event.Keydown; 433 | Event.Keyup; 434 | ] |> List.iter (fun evt_kind -> 435 | Window.add_event_listener window evt_kind on_js_event true 436 | ); 437 | ) 438 | -------------------------------------------------------------------------------- /playground/web/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name elm_playground_web) 3 | (implements elm_playground) 4 | (libraries 5 | vdom 6 | ; commons 7 | 8 | elm_playground 9 | ) 10 | ;(preprocess (pps profiling.ppx)) 11 | ) 12 | -------------------------------------------------------------------------------- /semgrep.jsonnet: -------------------------------------------------------------------------------- 1 | 2 | local ocaml = import 'p/ocaml'; 3 | 4 | //Temporary hack to not report p/ocaml findings on semgrep libs 5 | local ocaml_rules = 6 | [ 7 | r { paths: { exclude: ['libs/*', 'tools/*', 'languages/*'] } } 8 | for r in ocaml.rules 9 | ]; 10 | 11 | local semgrep_rules = [ 12 | { 13 | // Just an example 14 | id: 'no-open-in', 15 | match: { any: ['open_in_bin ...', 'open_in ...'] }, 16 | // Same but using The old syntax: 17 | // "pattern-either": [ 18 | // { pattern: "open_in_bin ..." }, 19 | // { pattern: "open_in ..." }, 20 | // ], 21 | languages: ['ocaml'], 22 | severity: 'ERROR', 23 | message: ||| 24 | It is easy to forget to close `open_in` with `close_in`. 25 | Use `Common.with_open_infile()` or `Chan.with_open_in` instead. 26 | |||, 27 | paths: { 28 | exclude: ['common2.ml'], 29 | }, 30 | }, 31 | ]; 32 | 33 | // ---------------------------------------------------------------------------- 34 | // Skip and last-minute override 35 | // ---------------------------------------------------------------------------- 36 | 37 | local todo_skipped_for_now = [ 38 | //TODO? what is the fix for that? 39 | 'ocaml.lang.portability.crlf-support.broken-input-line', 40 | // too noisy 41 | 'ocaml.lang.security.hashtable-dos.ocamllint-hashtable-dos', 42 | ]; 43 | 44 | local override_messages = { 45 | // Semgrep specific adjustments 46 | 'ocaml.lang.best-practice.exception.bad-reraise': ||| 47 | You should not re-raise exceptions using 'raise' because it loses track 48 | of where the exception was raised originally. See commons/Exception.mli 49 | for more information. 50 | Use `Exception.catch exn` and later `Exception.raise exn` or 51 | `Exception.catch_and_reraise exn` if there is no code between the moment 52 | you catch the exn and re-raise it. 53 | |||, 54 | }; 55 | 56 | // ---------------------------------------------------------------------------- 57 | // Entry point 58 | // ---------------------------------------------------------------------------- 59 | 60 | // TODO? Use TCB rules? 61 | 62 | local all = semgrep_rules; 63 | { 64 | rules: 65 | [ 66 | if std.objectHas(override_messages, r.id) 67 | then (r { message: override_messages[r.id] }) 68 | else r 69 | for r in all 70 | if !std.member(todo_skipped_for_now, r.id) 71 | ], 72 | } 73 | -------------------------------------------------------------------------------- /skip_list.txt: -------------------------------------------------------------------------------- 1 | dir: _build 2 | dir: tests 3 | 4 | 5 | -------------------------------------------------------------------------------- /system/Cmd.ml: -------------------------------------------------------------------------------- 1 | type 'msg t = None | Msg of 'msg 2 | let none = None 3 | -------------------------------------------------------------------------------- /system/Sub.ml: -------------------------------------------------------------------------------- 1 | type 'msg onesub = 2 | | SubTick of (Time.posix -> 'msg) 3 | | SubMouseMove of (float * float -> 'msg) 4 | | SubMouseDown of (unit -> 'msg) 5 | | SubMouseUp of (unit -> 'msg) 6 | | SubKeyDown of (Keyboard.key -> 'msg) 7 | | SubKeyUp of (Keyboard.key -> 'msg) 8 | 9 | 10 | type 'msg t = 'msg onesub list 11 | let none = [] 12 | let batch xs = (List.flatten xs) 13 | 14 | (* was in Event_.ml before *) 15 | let (on_animation_frame: (Time.posix -> 'msg) -> 'msg t) = fun f -> 16 | [SubTick f] 17 | 18 | let (on_mouse_move: (float * float -> 'msg) -> 'msg t) = fun f -> 19 | [SubMouseMove f] 20 | 21 | let (on_mouse_down: (unit -> 'msg) -> 'msg t) = fun f -> 22 | [SubMouseDown f] 23 | 24 | let (on_mouse_up: (unit -> 'msg) -> 'msg t) = fun f -> 25 | [SubMouseUp f] 26 | 27 | let (on_key_down: (Keyboard.key -> 'msg) -> 'msg t) = fun f -> 28 | [SubKeyDown f] 29 | 30 | let (on_key_up: (Keyboard.key -> 'msg) -> 'msg t) = fun f -> 31 | [SubKeyUp f] 32 | 33 | 34 | 35 | 36 | type event = 37 | | ETick of float 38 | | EMouseMove of (int * int) 39 | | EMouseButton of bool (* is_down = true *) 40 | | EKeyChanged of (bool (* down = true *) * Keyboard.key) 41 | 42 | let rec find_map_opt f = function 43 | | [] -> None 44 | | x::xs -> 45 | (match f x with 46 | | None -> find_map_opt f xs 47 | | Some x -> Some x 48 | ) 49 | 50 | let event_to_msgopt event subs = 51 | match event with 52 | | ETick time -> 53 | subs |> find_map_opt (function 54 | | SubTick f -> Some (f time) 55 | | _ -> None 56 | ) 57 | | EMouseMove (x, y) -> 58 | subs |> find_map_opt (function 59 | | SubMouseMove f -> 60 | Some (f (float_of_int x, float_of_int y)) 61 | | _ -> None 62 | ) 63 | | EMouseButton (true) -> 64 | subs |> find_map_opt (function 65 | | SubMouseDown f -> 66 | Some (f ()) 67 | | _ -> None 68 | ) 69 | | EMouseButton (false) -> 70 | subs |> find_map_opt (function 71 | | SubMouseUp f -> 72 | Some (f ()) 73 | | _ -> None 74 | ) 75 | | EKeyChanged (true, key) -> 76 | subs |> find_map_opt (function 77 | | SubKeyDown f -> 78 | Some (f key) 79 | | _ -> None 80 | ) 81 | | EKeyChanged (false, key) -> 82 | subs |> find_map_opt (function 83 | | SubKeyUp f -> 84 | Some (f key) 85 | | _ -> None 86 | ) 87 | -------------------------------------------------------------------------------- /system/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name elm_system) 3 | (wrapped false) 4 | (libraries 5 | ; commons 6 | elm_core 7 | ) 8 | ; (preprocess (pps profiling.ppx)) 9 | ) 10 | -------------------------------------------------------------------------------- /system/pad.txt: -------------------------------------------------------------------------------- 1 | was in core in elm 2 | 3 | -------------------------------------------------------------------------------- /tests/Svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/Svg.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of the ocaml-vdom package, released under the terms of an MIT-like license. *) 2 | (* See the attached LICENSE file. *) 3 | (* Copyright 2016 by LexiFi. *) 4 | 5 | open Vdom 6 | 7 | type model = int 8 | 9 | let update n = function 10 | | `Click -> n mod 5 + 1 11 | 12 | let init = 1 13 | 14 | let view n = 15 | div 16 | [ 17 | text (string_of_int n); 18 | svg_elt "svg" 19 | ~a:[ 20 | int_attr "width" (n * 20); 21 | int_attr "height" (n * 20); 22 | ] 23 | [ 24 | svg_elt "circle" [] 25 | ~a:[ 26 | onclick (fun _ -> `Click); 27 | int_attr "cx" (n * 10); 28 | int_attr "cy" (n * 10); 29 | int_attr "r" (n * 10); 30 | attr "fill" (if n mod 2 = 0 then "green" else "blue"); 31 | ] 32 | ] 33 | ] 34 | 35 | let app = simple_app ~init ~view ~update () 36 | 37 | 38 | open Js_browser 39 | 40 | let run () = Vdom_blit.run app |> Vdom_blit.dom |> Element.append_child (Document.body document) 41 | let () = Window.set_onload window run 42 | -------------------------------------------------------------------------------- /tests/Test_cairo_graphics.ml: -------------------------------------------------------------------------------- 1 | (* from cairo/examples/graphics_demo.ml *) 2 | 3 | (* Demo to show how one could achieve cairo drawing on a Graphics 4 | window. Note that using XLib or GTK would be *much* faster. *) 5 | 6 | open Printf 7 | open Cairo 8 | 9 | let pi2 = 8. *. atan 1. 10 | 11 | let lastfps = ref (Unix.gettimeofday ()) 12 | let frames = ref 0 13 | let fps = ref 0. 14 | 15 | let update_fps () = 16 | let t = Unix.gettimeofday () in 17 | let dt = t -. !lastfps in 18 | if dt > 0.5 then ( 19 | fps := float !frames /. dt; 20 | frames := 0; 21 | lastfps := t 22 | ); 23 | incr frames 24 | 25 | let draw cr width height x y = 26 | let x = x -. width *. 0.5 and y = y -. height *. 0.5 in 27 | let r = 0.5 *. sqrt (x *. x +. y *. y) in 28 | set_source_rgba cr 0. 1. 0. 0.5; 29 | arc cr (0.5 *. width) (0.35 *. height) r 0. pi2; 30 | fill cr; 31 | set_source_rgba cr 1. 0. 0. 0.5; 32 | arc cr (0.35 *. width) (0.65 *. height) r 0. pi2; 33 | fill cr; 34 | set_source_rgba cr 0. 0. 1. 0.5; 35 | arc cr (0.65 *. width) (0.65 *. height) r 0. pi2; 36 | fill cr; 37 | set_source_rgba cr 1. 1. 0. 1.; 38 | move_to cr (0.05 *. width) (0.95 *. height); 39 | show_text cr (sprintf "%gx%g -- %.0f fps" width height !fps) 40 | 41 | let expose () = 42 | let sx = Graphics.size_x () 43 | and sy = Graphics.size_y () 44 | and mx, my = Graphics.mouse_pos () in 45 | (* Create a cairo context from a cairo surface and do our drawings 46 | on it. Note: we may cache it between expose events for 47 | incremental drawings but its creation and initialization is not 48 | the time bottleneck here. *) 49 | let cr_img = Image.create Image.RGB24 sx sy in 50 | let cr = create cr_img in 51 | draw cr (float sx) (float sy) (float mx) (float my); 52 | (* Don't forget to flush the surface before using its content. *) 53 | Surface.flush cr_img; 54 | (* Now, access the surface data and convert it to a Graphics.image 55 | that can be drawn on the Graphics window. *) 56 | let data32 = Image.get_data32 cr_img in 57 | let data_img = 58 | Array.init sy 59 | (fun y -> Array.init sx (fun x -> Int32.to_int (data32.{y, x}))) 60 | in 61 | Graphics.draw_image (Graphics.make_image data_img) 0 0; 62 | Graphics.synchronize (); 63 | (* Update our fps counter. *) 64 | update_fps () 65 | 66 | let () = 67 | Graphics.open_graph ""; 68 | Graphics.auto_synchronize false; 69 | while true do 70 | expose () 71 | done 72 | -------------------------------------------------------------------------------- /tests/Test_ocamlsdl2.ml: -------------------------------------------------------------------------------- 1 | (* from ocamlsdl2/examples/ex_event.ml *) 2 | [@@@warning "-9"] 3 | 4 | open Sdlevent 5 | open Sdl 6 | 7 | let print_keyboard_event e st = 8 | Printf.printf " 9 | keyboard_event (( 10 | timestamp: %ld 11 | window_id: %lX 12 | state: %s / %s 13 | repeat: %d 14 | scancode: %s 15 | keycode: %s 16 | keymod: %s 17 | ))\n%!" 18 | e.ke_timestamp 19 | e.ke_window_id 20 | (Sdlevent.string_of_state e.ke_state) st 21 | e.ke_repeat 22 | (Sdlscancode.to_string e.scancode) 23 | (Sdlkeycode.to_string e.keycode) 24 | (String.concat " " 25 | (List.map Sdlkeymod.to_string e.keymod)) 26 | 27 | let print_mouse_motion_event e = 28 | Printf.printf " 29 | mouse_motion_event (( 30 | timestamp: %ld 31 | window_id: %lX 32 | buttons: %s 33 | x: %d 34 | y: %d 35 | xrel: %d 36 | yrel: %d 37 | ))\n%!" 38 | e.mm_timestamp 39 | e.mm_window_id 40 | (String.concat " " 41 | (List.map string_of_int e.mm_buttons)) 42 | e.mm_x 43 | e.mm_y 44 | e.mm_xrel 45 | e.mm_yrel 46 | 47 | let print_mouse_button_event e s = 48 | Printf.printf " 49 | mouse_button_event (( 50 | timestamp: %ld 51 | window_id: %lX 52 | button: %d 53 | state: %s %s 54 | x: %d 55 | y: %d 56 | ))\n%!" 57 | e.mb_timestamp 58 | e.mb_window_id 59 | e.mb_button 60 | (Sdlevent.string_of_state e.mb_state) s 61 | e.mb_x 62 | e.mb_y 63 | 64 | let print_mouse_wheel_event e = 65 | Printf.printf " 66 | mouse_wheel_event (( 67 | timestamp: %ld 68 | window_id: %lX 69 | x: %d 70 | y: %d 71 | ))\n%!" 72 | e.mw_timestamp 73 | e.mw_window_id 74 | e.mw_x 75 | e.mw_y 76 | 77 | let print_joy_axis_event e = 78 | Printf.printf " 79 | joy_axis_event (( 80 | timestamp: %ld 81 | which: %d 82 | axis: %d 83 | value: %d 84 | ))\n%!" 85 | e.ja_timestamp 86 | e.ja_which 87 | e.ja_axis 88 | e.ja_value 89 | 90 | let print_joy_button_event e s = 91 | Printf.printf " 92 | joy_button_event (( 93 | timestamp: %ld 94 | which: %d 95 | button: %d 96 | state: %s %s 97 | ))\n%!" 98 | e.jb_timestamp 99 | e.jb_which 100 | e.jb_button 101 | (Sdlevent.string_of_state e.jb_state) s 102 | 103 | let print_joy_hat_event e = 104 | Printf.printf " 105 | joy_hat_event (( 106 | timestamp: %ld 107 | which: %d 108 | hat: %d 109 | pos: \t left:%b \t right:%b \t up:%b \t down:%b 110 | # %s 111 | # %s 112 | ))\n%!" 113 | e.jh_timestamp 114 | e.jh_which 115 | e.jh_hat 116 | e.jh_pos.Hat.left 117 | e.jh_pos.Hat.right 118 | e.jh_pos.Hat.up 119 | e.jh_pos.Hat.down 120 | (Sdlhat.string_of_pos e.jh_pos) 121 | (Sdlhat.string_of_dir e.jh_dir) 122 | 123 | let print_joy_device_event e ch = 124 | Printf.printf " 125 | joy_device_event (( 126 | timestamp: %ld 127 | which: %d 128 | device_change: %s %s 129 | ))\n%!" 130 | e.jd_timestamp 131 | e.jd_which 132 | (Sdlevent.string_of_joy_device_change e.jd_change) ch 133 | 134 | let print_window_event e = 135 | Printf.printf " 136 | window_event (( 137 | timestamp: %ld 138 | window_ID: %ld 139 | kind: %s 140 | ))\n%!" 141 | e.we_timestamp 142 | e.window_ID 143 | (Sdlevent.string_of_window_event_kind e.kind) 144 | 145 | 146 | let proc_events = function 147 | | KeyDown { scancode = Sdlscancode.ESCAPE } -> 148 | print_endline "Goodbye"; 149 | exit 0 150 | | KeyDown { keycode = Sdlkeycode.Escape } -> 151 | print_endline "Fuck you!"; 152 | exit 0 153 | | KeyDown e -> print_keyboard_event e "down" 154 | | KeyUp e -> print_keyboard_event e "up" 155 | | Mouse_Motion e -> print_mouse_motion_event e 156 | | Mouse_Button_Down e -> print_mouse_button_event e "down" 157 | | Mouse_Button_Up e -> print_mouse_button_event e "up" 158 | | Mouse_Wheel e -> print_mouse_wheel_event e 159 | | Joy_Axis_Motion e -> print_joy_axis_event e 160 | | Joy_Button_Down e -> print_joy_button_event e "down" 161 | | Joy_Button_Up e -> print_joy_button_event e "up" 162 | | Joy_Hat_Motion e -> print_joy_hat_event e 163 | | Joy_Device_Removed e -> print_joy_device_event e "Removed" 164 | | Joy_Device_Added e -> print_joy_device_event e "Added"; 165 | ignore(Sdljoystick.j_open e.jd_which); 166 | | Window_Event { kind = WindowEvent_Resized p } -> 167 | Printf.printf "### WINDOW_RESIZED => %d x %d\n" p.win_x p.win_y 168 | | Window_Event e -> print_window_event e 169 | | Quit e -> 170 | Printf.printf "Quit(timestamp:%ld)\n%!" e.quit_timestamp; 171 | Sdl.quit (); 172 | exit 0 173 | | e -> 174 | print_endline (Sdlevent.to_string e) 175 | 176 | 177 | let () = 178 | let width, height = (640, 480) in 179 | Sdl.init [`VIDEO; `JOYSTICK]; 180 | at_exit print_newline; 181 | (* 182 | let window, renderer = 183 | Sdlrender.create_window_and_renderer ~width ~height 184 | in 185 | ignore (window, renderer); 186 | *) 187 | let window = 188 | Sdlwindow.create2 189 | ~title:"SDL Event" 190 | ~x:`undefined ~y:`undefined 191 | ~width ~height 192 | ~flags:[ 193 | (* 194 | Sdlwindow.FullScreen; 195 | Sdlwindow.OpenGL; 196 | Sdlwindow.Shown; 197 | Sdlwindow.Hidden; 198 | Sdlwindow.Borderless; 199 | Sdlwindow.Resizable; 200 | Sdlwindow.Minimized; 201 | Sdlwindow.Maximized; 202 | Sdlwindow.Input_Grabbed; 203 | Sdlwindow.Input_Focus; 204 | Sdlwindow.Mouse_Focus; 205 | Sdlwindow.FullScreen_Desktop; 206 | Sdlwindow.Foreign; 207 | *) 208 | ] 209 | in 210 | ignore (window); 211 | 212 | let joy_num = Sdljoystick.num_joysticks () in 213 | for i = 0 to pred joy_num do 214 | ignore(Sdljoystick.j_open i); 215 | let name = Sdljoystick.name_for_index i in 216 | Printf.printf "Joy-name: %s\n" name; 217 | done; 218 | 219 | let rec event_loop () = 220 | match Sdlevent.poll_event () with 221 | | Some ev -> proc_events ev; event_loop () 222 | | None -> () 223 | in 224 | let rec main_loop () = 225 | event_loop (); 226 | Printf.printf ".%!"; 227 | Sdltimer.delay 20; 228 | main_loop () 229 | in 230 | main_loop () 231 | -------------------------------------------------------------------------------- /tests/Test_vdom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/Test_vdom.ml: -------------------------------------------------------------------------------- 1 | open Vdom 2 | 3 | type model = string 4 | 5 | let update _ = function 6 | | `Click -> 7 | "Click:" 8 | | `Keydown s -> 9 | "Keydown:" ^ s 10 | 11 | let init = "EMPTY" 12 | 13 | let view s = 14 | div 15 | ~a:[ 16 | onkeydown (fun evt -> `Keydown (Printf.sprintf "%d" evt.which)); 17 | autofocus; 18 | ] 19 | [ 20 | div [text (Printf.sprintf "Content: %s" s)]; 21 | div [input [] ~a:[onclick (fun _ -> `Click); type_button; value "Update"]] 22 | ] 23 | 24 | let app = simple_app ~init ~view ~update () 25 | 26 | 27 | open Js_browser 28 | 29 | let run () = Vdom_blit.run app |> Vdom_blit.dom |> Element.append_child (Document.body document) 30 | let () = Window.set_onload window run 31 | -------------------------------------------------------------------------------- /tests/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names 3 | ; Test_cairo_graphics 4 | Test_tsdl 5 | ; Test_ocamlsdl2 6 | ) 7 | (libraries 8 | cairo2 9 | ; graphics 10 | tsdl 11 | ; sdl2 12 | ) 13 | ) 14 | --------------------------------------------------------------------------------