├── .gitignore ├── LICENCE ├── README.md ├── assets ├── font.fnt ├── font.png ├── fonts │ ├── MasterOfComics.bin │ ├── MasterOfComics_sheet0.png │ ├── arial-sdf.bin │ ├── arial-sdf.png │ ├── digitalt.bin │ ├── digitalt_sheet0.png │ └── master_of_comics │ │ ├── Master of Comics Back Personal Use.ttf │ │ ├── Master of Comics Personal Use.ttf │ │ ├── Master-of-Comics.png │ │ └── Terms of Use End User Lisence Agreement.txt ├── roboto.fnt └── roboto.png ├── bsconfig.json ├── index.html ├── package.json ├── require_polyfill.js ├── src ├── background.re ├── config.re ├── currEl.re ├── drawElement.re ├── dropBeams.re ├── game.re ├── gridLight.re ├── gridProgram.re ├── index.re ├── mandelbrot.re ├── mask.re ├── sceneState.re ├── sdfTiles.re ├── tetronimo.re ├── tileBeam.re ├── tileBlink.re ├── tileShadows.re └── tilesDraw.re ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | .merlin 3 | node_modules 4 | *.cmo 5 | *.cmx 6 | *.o 7 | **/.DS_Store 8 | dist -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gudmund Vatn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vimtris 2 | --- 3 | Tetris game built on [rainbow-ui](https://github.com/codewaterlabs/rainbow-ui), which is built on [reasongl](https://github.com/bsansouci/reasongl). 4 | Uses vim keys, so the name. Was made to learn reasonml, while also maybe 5 | getting a game to play and relax a little between programming sessions. 6 | The "rainbow-ui" framework evolved from this. 7 | 8 | Since it is built on reasongl, it is along the way to working as a native project, but currently there are web-specific code several places. 9 | 10 | To install, I need to use ```yarn install```, then I use ```yarn build:web```. -------------------------------------------------------------------------------- /assets/font.fnt: -------------------------------------------------------------------------------- 1 | info face=font size=32 bold=0 italic=0 charset= unicode= stretchH=100 smooth=1 aa=1 padding=3,3,3,3 spacing=0,0 outline=0 2 | common lineHeight=36 base=25 scaleW=256 scaleH=256 pages=1 packed=0 3 | page id=0 file="font.png" 4 | chars count=91 5 | char id=97 x=3 y=3 width=16 height=18 xoffset=1 yoffset=8 xadvance=18 page=0 chnl=15 6 | char id=98 x=3 y=24 width=15 height=24 xoffset=2 yoffset=2 xadvance=18 page=0 chnl=15 7 | char id=99 x=3 y=51 width=15 height=18 xoffset=1 yoffset=8 xadvance=16 page=0 chnl=15 8 | char id=100 x=3 y=72 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 9 | char id=101 x=3 y=99 width=16 height=18 xoffset=1 yoffset=8 xadvance=18 page=0 chnl=15 10 | char id=102 x=21 y=24 width=10 height=24 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=15 11 | char id=103 x=21 y=51 width=15 height=24 xoffset=1 yoffset=8 xadvance=18 page=0 chnl=15 12 | char id=104 x=34 y=3 width=14 height=23 xoffset=2 yoffset=2 xadvance=18 page=0 chnl=15 13 | char id=105 x=3 y=120 width=3 height=23 xoffset=2 yoffset=2 xadvance=7 page=0 chnl=15 14 | char id=106 x=3 y=146 width=7 height=30 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=15 15 | char id=107 x=9 y=120 width=14 height=23 xoffset=2 yoffset=2 xadvance=16 page=0 chnl=15 16 | char id=108 x=22 y=78 width=3 height=23 xoffset=2 yoffset=2 xadvance=7 page=0 chnl=15 17 | char id=109 x=34 y=29 width=23 height=17 xoffset=2 yoffset=8 xadvance=27 page=0 chnl=15 18 | char id=110 x=51 y=3 width=14 height=17 xoffset=2 yoffset=8 xadvance=18 page=0 chnl=15 19 | char id=111 x=3 y=179 width=16 height=18 xoffset=1 yoffset=8 xadvance=18 page=0 chnl=15 20 | char id=112 x=13 y=146 width=15 height=24 xoffset=2 yoffset=8 xadvance=18 page=0 chnl=15 21 | char id=113 x=26 y=104 width=15 height=24 xoffset=1 yoffset=8 xadvance=18 page=0 chnl=15 22 | char id=114 x=22 y=3 width=9 height=17 xoffset=2 yoffset=8 xadvance=11 page=0 chnl=15 23 | char id=115 x=28 y=78 width=14 height=18 xoffset=1 yoffset=8 xadvance=16 page=0 chnl=15 24 | char id=116 x=39 y=49 width=8 height=23 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=15 25 | char id=117 x=3 y=200 width=14 height=17 xoffset=2 yoffset=8 xadvance=18 page=0 chnl=15 26 | char id=118 x=3 y=220 width=16 height=17 xoffset=0 yoffset=8 xadvance=16 page=0 chnl=15 27 | char id=119 x=20 y=200 width=23 height=17 xoffset=0 yoffset=8 xadvance=23 page=0 chnl=15 28 | char id=120 x=22 y=173 width=16 height=17 xoffset=0 yoffset=8 xadvance=16 page=0 chnl=15 29 | char id=121 x=22 y=220 width=16 height=24 xoffset=1 yoffset=8 xadvance=16 page=0 chnl=15 30 | char id=122 x=41 y=220 width=15 height=17 xoffset=1 yoffset=8 xadvance=16 page=0 chnl=15 31 | char id=65 x=31 y=131 width=22 height=23 xoffset=0 yoffset=2 xadvance=21 page=0 chnl=15 32 | char id=66 x=44 y=99 width=18 height=23 xoffset=2 yoffset=2 xadvance=21 page=0 chnl=15 33 | char id=67 x=41 y=157 width=21 height=24 xoffset=2 yoffset=2 xadvance=23 page=0 chnl=15 34 | char id=68 x=56 y=125 width=19 height=23 xoffset=2 yoffset=2 xadvance=23 page=0 chnl=15 35 | char id=69 x=46 y=184 width=17 height=23 xoffset=3 yoffset=2 xadvance=21 page=0 chnl=15 36 | char id=70 x=65 y=151 width=16 height=23 xoffset=3 yoffset=2 xadvance=20 page=0 chnl=15 37 | char id=71 x=59 y=210 width=22 height=24 xoffset=2 yoffset=2 xadvance=25 page=0 chnl=15 38 | char id=72 x=66 y=177 width=18 height=23 xoffset=3 yoffset=2 xadvance=23 page=0 chnl=15 39 | char id=73 x=50 y=49 width=3 height=23 xoffset=3 yoffset=2 xadvance=9 page=0 chnl=15 40 | char id=74 x=56 y=49 width=13 height=24 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=15 41 | char id=75 x=60 y=23 width=19 height=23 xoffset=2 yoffset=2 xadvance=21 page=0 chnl=15 42 | char id=76 x=65 y=76 width=15 height=23 xoffset=2 yoffset=2 xadvance=18 page=0 chnl=15 43 | char id=77 x=72 y=49 width=22 height=23 xoffset=2 yoffset=2 xadvance=27 page=0 chnl=15 44 | char id=78 x=82 y=3 width=18 height=23 xoffset=2 yoffset=2 xadvance=23 page=0 chnl=15 45 | char id=79 x=78 y=102 width=22 height=24 xoffset=2 yoffset=2 xadvance=25 page=0 chnl=15 46 | char id=80 x=83 y=75 width=18 height=23 xoffset=2 yoffset=2 xadvance=21 page=0 chnl=15 47 | char id=81 x=97 y=29 width=23 height=25 xoffset=1 yoffset=2 xadvance=25 page=0 chnl=15 48 | char id=82 x=103 y=3 width=20 height=23 xoffset=3 yoffset=2 xadvance=23 page=0 chnl=15 49 | char id=83 x=84 y=129 width=19 height=24 xoffset=1 yoffset=2 xadvance=21 page=0 chnl=15 50 | char id=84 x=103 y=101 width=19 height=23 xoffset=1 yoffset=2 xadvance=20 page=0 chnl=15 51 | char id=85 x=104 y=57 width=18 height=24 xoffset=3 yoffset=2 xadvance=23 page=0 chnl=15 52 | char id=86 x=123 y=29 width=21 height=23 xoffset=0 yoffset=2 xadvance=21 page=0 chnl=15 53 | char id=87 x=126 y=3 width=30 height=23 xoffset=0 yoffset=2 xadvance=30 page=0 chnl=15 54 | char id=88 x=84 y=203 width=21 height=23 xoffset=0 yoffset=2 xadvance=21 page=0 chnl=15 55 | char id=89 x=84 y=229 width=21 height=23 xoffset=0 yoffset=2 xadvance=21 page=0 chnl=15 56 | char id=90 x=87 y=156 width=18 height=23 xoffset=1 yoffset=2 xadvance=20 page=0 chnl=15 57 | char id=48 x=106 y=127 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 58 | char id=49 x=108 y=154 width=9 height=23 xoffset=3 yoffset=2 xadvance=18 page=0 chnl=15 59 | char id=50 x=108 y=180 width=16 height=23 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 60 | char id=51 x=108 y=206 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 61 | char id=52 x=120 y=154 width=16 height=23 xoffset=0 yoffset=2 xadvance=18 page=0 chnl=15 62 | char id=53 x=124 y=127 width=16 height=23 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 63 | char id=54 x=126 y=206 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 64 | char id=55 x=127 y=180 width=15 height=23 xoffset=2 yoffset=2 xadvance=18 page=0 chnl=15 65 | char id=56 x=139 y=153 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 66 | char id=57 x=144 y=206 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 67 | char id=33 x=145 y=180 width=4 height=23 xoffset=3 yoffset=2 xadvance=9 page=0 chnl=15 68 | char id=8470 x=152 y=180 width=31 height=23 xoffset=3 yoffset=2 xadvance=34 page=0 chnl=15 69 | char id=59 x=45 y=75 width=4 height=21 xoffset=3 yoffset=8 xadvance=9 page=0 chnl=15 70 | char id=37 x=162 y=206 width=25 height=24 xoffset=2 yoffset=2 xadvance=28 page=0 chnl=15 71 | char id=58 x=68 y=3 width=4 height=17 xoffset=3 yoffset=8 xadvance=9 page=0 chnl=15 72 | char id=63 x=125 y=55 width=15 height=24 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=15 73 | char id=42 x=82 y=29 width=11 height=10 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=15 74 | char id=40 x=125 y=82 width=8 height=30 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 75 | char id=41 x=136 y=82 width=8 height=30 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 76 | char id=95 x=22 y=193 width=19 height=2 xoffset=0 yoffset=29 xadvance=18 page=0 chnl=15 77 | char id=43 x=59 y=237 width=15 height=15 xoffset=2 yoffset=6 xadvance=19 page=0 chnl=15 78 | char id=45 x=44 y=125 width=9 height=3 xoffset=1 yoffset=15 xadvance=11 page=0 chnl=15 79 | char id=61 x=41 y=240 width=15 height=10 xoffset=2 yoffset=9 xadvance=19 page=0 chnl=15 80 | char id=46 x=82 y=42 width=4 height=4 xoffset=3 yoffset=22 xadvance=9 page=0 chnl=15 81 | char id=44 x=97 y=57 width=4 height=8 xoffset=3 yoffset=22 xadvance=9 page=0 chnl=15 82 | char id=47 x=143 y=55 width=9 height=24 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=15 83 | char id=124 x=143 y=115 width=3 height=30 xoffset=3 yoffset=2 xadvance=8 page=0 chnl=15 84 | char id=92 x=147 y=82 width=9 height=24 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=15 85 | char id=34 x=125 y=115 width=9 height=8 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=15 86 | char id=39 x=77 y=237 width=4 height=8 xoffset=1 yoffset=2 xadvance=6 page=0 chnl=15 87 | char id=64 x=149 y=109 width=30 height=30 xoffset=2 yoffset=2 xadvance=32 page=0 chnl=15 88 | char id=35 x=157 y=142 width=17 height=24 xoffset=0 yoffset=2 xadvance=18 page=0 chnl=15 89 | char id=36 x=177 y=142 width=16 height=29 xoffset=1 yoffset=0 xadvance=18 page=0 chnl=15 90 | char id=94 x=3 y=240 width=14 height=13 xoffset=1 yoffset=2 xadvance=15 page=0 chnl=15 91 | char id=38 x=186 y=174 width=20 height=24 xoffset=1 yoffset=2 xadvance=21 page=0 chnl=15 92 | char id=123 x=155 y=29 width=9 height=30 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=15 93 | char id=125 x=159 y=62 width=9 height=30 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=15 94 | char id=91 x=190 y=201 width=7 height=30 xoffset=2 yoffset=2 xadvance=9 page=0 chnl=15 95 | char id=93 x=200 y=201 width=7 height=30 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=15 96 | char id=32 x=0 y=0 width=0 height=0 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=15 97 | kernings count=97 98 | kerning first=32 second=65 amount=-2 99 | kerning first=32 second=84 amount=-1 100 | kerning first=32 second=89 amount=-1 101 | kerning first=49 second=49 amount=-2 102 | kerning first=65 second=32 amount=-2 103 | kerning first=65 second=84 amount=-2 104 | kerning first=65 second=86 amount=-2 105 | kerning first=65 second=87 amount=-1 106 | kerning first=65 second=89 amount=-2 107 | kerning first=65 second=118 amount=-1 108 | kerning first=65 second=119 amount=-1 109 | kerning first=65 second=121 amount=-1 110 | kerning first=70 second=44 amount=-4 111 | kerning first=70 second=46 amount=-4 112 | kerning first=70 second=65 amount=-2 113 | kerning first=76 second=32 amount=-1 114 | kerning first=76 second=84 amount=-2 115 | kerning first=76 second=86 amount=-2 116 | kerning first=76 second=87 amount=-2 117 | kerning first=76 second=89 amount=-2 118 | kerning first=76 second=121 amount=-1 119 | kerning first=80 second=32 amount=-1 120 | kerning first=80 second=44 amount=-4 121 | kerning first=80 second=46 amount=-4 122 | kerning first=80 second=65 amount=-2 123 | kerning first=82 second=84 amount=-1 124 | kerning first=82 second=86 amount=-1 125 | kerning first=82 second=87 amount=-1 126 | kerning first=82 second=89 amount=-1 127 | kerning first=84 second=32 amount=-1 128 | kerning first=84 second=44 amount=-4 129 | kerning first=84 second=45 amount=-2 130 | kerning first=84 second=46 amount=-4 131 | kerning first=84 second=58 amount=-4 132 | kerning first=84 second=59 amount=-4 133 | kerning first=84 second=65 amount=-2 134 | kerning first=84 second=79 amount=-1 135 | kerning first=84 second=97 amount=-4 136 | kerning first=84 second=99 amount=-4 137 | kerning first=84 second=101 amount=-4 138 | kerning first=84 second=105 amount=-1 139 | kerning first=84 second=111 amount=-4 140 | kerning first=84 second=114 amount=-1 141 | kerning first=84 second=115 amount=-4 142 | kerning first=84 second=117 amount=-1 143 | kerning first=84 second=119 amount=-2 144 | kerning first=84 second=121 amount=-2 145 | kerning first=86 second=44 amount=-3 146 | kerning first=86 second=45 amount=-2 147 | kerning first=86 second=46 amount=-3 148 | kerning first=86 second=58 amount=-1 149 | kerning first=86 second=59 amount=-1 150 | kerning first=86 second=65 amount=-2 151 | kerning first=86 second=97 amount=-2 152 | kerning first=86 second=101 amount=-2 153 | kerning first=86 second=105 amount=-1 154 | kerning first=86 second=111 amount=-2 155 | kerning first=86 second=114 amount=-1 156 | kerning first=86 second=117 amount=-1 157 | kerning first=86 second=121 amount=-1 158 | kerning first=87 second=44 amount=-2 159 | kerning first=87 second=45 amount=-1 160 | kerning first=87 second=46 amount=-2 161 | kerning first=87 second=58 amount=-1 162 | kerning first=87 second=59 amount=-1 163 | kerning first=87 second=65 amount=-1 164 | kerning first=87 second=97 amount=-1 165 | kerning first=87 second=101 amount=-1 166 | kerning first=87 second=105 amount=0 167 | kerning first=87 second=111 amount=-1 168 | kerning first=87 second=114 amount=-1 169 | kerning first=87 second=117 amount=-1 170 | kerning first=87 second=121 amount=0 171 | kerning first=89 second=32 amount=-1 172 | kerning first=89 second=44 amount=-4 173 | kerning first=89 second=45 amount=-3 174 | kerning first=89 second=46 amount=-4 175 | kerning first=89 second=58 amount=-2 176 | kerning first=89 second=59 amount=-2 177 | kerning first=89 second=65 amount=-2 178 | kerning first=89 second=97 amount=-2 179 | kerning first=89 second=101 amount=-3 180 | kerning first=89 second=105 amount=-1 181 | kerning first=89 second=111 amount=-3 182 | kerning first=89 second=112 amount=-2 183 | kerning first=89 second=113 amount=-3 184 | kerning first=89 second=117 amount=-2 185 | kerning first=89 second=118 amount=-2 186 | kerning first=102 second=102 amount=-1 187 | kerning first=114 second=44 amount=-2 188 | kerning first=114 second=46 amount=-2 189 | kerning first=118 second=44 amount=-2 190 | kerning first=118 second=46 amount=-2 191 | kerning first=119 second=44 amount=-2 192 | kerning first=119 second=46 amount=-2 193 | kerning first=121 second=44 amount=-2 194 | kerning first=121 second=46 amount=-2 -------------------------------------------------------------------------------- /assets/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/font.png -------------------------------------------------------------------------------- /assets/fonts/MasterOfComics.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/MasterOfComics.bin -------------------------------------------------------------------------------- /assets/fonts/MasterOfComics_sheet0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/MasterOfComics_sheet0.png -------------------------------------------------------------------------------- /assets/fonts/arial-sdf.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/arial-sdf.bin -------------------------------------------------------------------------------- /assets/fonts/arial-sdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/arial-sdf.png -------------------------------------------------------------------------------- /assets/fonts/digitalt.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/digitalt.bin -------------------------------------------------------------------------------- /assets/fonts/digitalt_sheet0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/digitalt_sheet0.png -------------------------------------------------------------------------------- /assets/fonts/master_of_comics/Master of Comics Back Personal Use.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/master_of_comics/Master of Comics Back Personal Use.ttf -------------------------------------------------------------------------------- /assets/fonts/master_of_comics/Master of Comics Personal Use.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/master_of_comics/Master of Comics Personal Use.ttf -------------------------------------------------------------------------------- /assets/fonts/master_of_comics/Master-of-Comics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/fonts/master_of_comics/Master-of-Comics.png -------------------------------------------------------------------------------- /assets/fonts/master_of_comics/Terms of Use End User Lisence Agreement.txt: -------------------------------------------------------------------------------- 1 | 2 | TERMS OF USE 3 | ----------- 4 | 5 | Billy Argel Fonts. 6 | By Billy Argel /Billy Argel Fonts (copyright) Free for personal use only. 7 | Contact me for any Commercial or promotional Use to purchase a license. 8 | "personal use only" does not constitut public domain or free. 9 | 10 | Contact mail: billyargel@gmail.com 11 | COMMERCIAL LICENSES,MORE FONTS AND COMPLETE SETS > www.billyargel.com 12 | 13 | 14 | Terms of Use/End User License Agreement 15 | 16 | Upon downloading this font the user bound to abide 17 | the Free Font Terms of Use/Free Font End User 18 | License Agreement (TOU/EULA) defined below. 19 | This agreement pertains to you/the user and the 20 | liscencing rights aquired with this download. These 21 | rights are specific to free download and usage of this 22 | font, i.e. a personal use License agreement. 23 | ----------------------------------------------- 24 | Free Font Terms of Use 25 | Free Font End User License Agreement: 26 | ----------------------------------------------- 27 | 1.) This font is free for personal use. 28 | 29 | 2.) The distribution of this font for financial gain or 30 | profit is not permitted under any circumstances and is 31 | strictly prohibited. Do not add this font to a font CD or 32 | compilation and or archive that is to be sold for a profit. 33 | 34 | Basically, don't sell this font, and do not make things 35 | that are to be sold with this font... It's free for personal 36 | use only. Commercial use requires the user to obtain a 37 | Commercial Font End User License.To obtain a commercial license, 38 | contact me: billyargel@gmail.com for more details. 39 | 40 | For profit scrapbooking and digital scrapbooking material (s) or 41 | scrapbooking and digital scrapbooking material(s) intended to 42 | be sold/resold with this font are also prohibited under this license, 43 | and require a commercial License. To obtain a Commercial 44 | Font End User License for this font billyargel@gmail.com 45 | Please note, scrapbooking and digital scrapbooking use of this 46 | font is only restricted when pertaining to the making of any for 47 | profit items. Otherwise creating items to be used "personally," 48 | whether in a traditional scrapbook or digital scrapbook is entirely 49 | permitted under this free License. 50 | 51 | 3.) This font file must be kept intact as downloaded. Under no 52 | circumstances may this font file itself be edited, altered, or modified 53 | at any time or in any measure. This prohibits and indcludes but is 54 | not limited to, renaming this font file, as well as the creation of 55 | so-called "new" and/or derivitave fonts from this font file or any 56 | other possible digital representations. 57 | 58 | 4.) Upon downloading this font, the user accepts all liability and 59 | sole responsibility for the font file and any accompanying files 60 | therein. Billy Argel Fonts is not responsible or 61 | liable for any damages, loss or other consequences incurred 62 | as a result of downloading this font, or otherwise relating or 63 | associated with the download. 64 | 65 | *Personal use does not constitute "public domain or free." 66 | 67 | **If you are interested in the commercial use of this font or using 68 | this font in any manner outside the realm of "personal use," you 69 | must obtain the rights to use this font commercially 70 | (Commercial Font End User License) prior to doing so. To obtain 71 | a Commercial Font End User License Agreement with this font, 72 | contact me billyargel@gmail.com 73 | Thanks for accepting these terms. 74 | 75 | COMMERCIAL LICENSES,MORE FONTS AND COMPLETE SETS > www.billyargel.com 76 | 77 | 78 | -------------------------------------------------------------------------------- /assets/roboto.fnt: -------------------------------------------------------------------------------- 1 | info face="Roboto" size=13 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2 2 | common lineHeight=17 base=13 scaleW=512 scaleH=512 pages=1 packed=0 3 | page id=0 file="roboto.png" 4 | chars count=97 5 | char id=0 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=11 xadvance=0 page=0 chnl=0 6 | char id=10 x=0 y=0 width=15 height=20 xoffset=-1 yoffset=-3 xadvance=13 page=0 chnl=0 7 | char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=11 xadvance=3 page=0 chnl=0 8 | char id=33 x=27 y=20 width=5 height=11 xoffset=-1 yoffset=2 xadvance=3 page=0 chnl=0 9 | char id=34 x=249 y=20 width=6 height=5 xoffset=-1 yoffset=1 xadvance=5 page=0 chnl=0 10 | char id=35 x=56 y=20 width=10 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 11 | char id=36 x=72 y=0 width=9 height=14 xoffset=-1 yoffset=0 xadvance=8 page=0 chnl=0 12 | char id=37 x=45 y=20 width=11 height=11 xoffset=-1 yoffset=2 xadvance=10 page=0 chnl=0 13 | char id=38 x=66 y=20 width=11 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 14 | char id=39 x=255 y=20 width=4 height=5 xoffset=-1 yoffset=1 xadvance=3 page=0 chnl=0 15 | char id=40 x=15 y=0 width=7 height=16 xoffset=-1 yoffset=0 xadvance=4 page=0 chnl=0 16 | char id=41 x=22 y=0 width=6 height=16 xoffset=-1 yoffset=0 xadvance=5 page=0 chnl=0 17 | char id=42 x=222 y=20 width=9 height=8 xoffset=-1 yoffset=2 xadvance=6 page=0 chnl=0 18 | char id=43 x=199 y=20 width=8 height=9 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 19 | char id=44 x=238 y=20 width=4 height=6 xoffset=-1 yoffset=10 xadvance=3 page=0 chnl=0 20 | char id=45 x=279 y=20 width=6 height=3 xoffset=-1 yoffset=7 xadvance=4 page=0 chnl=0 21 | char id=46 x=274 y=20 width=5 height=3 xoffset=-1 yoffset=10 xadvance=3 page=0 chnl=0 22 | char id=47 x=177 y=0 width=7 height=12 xoffset=-1 yoffset=2 xadvance=5 page=0 chnl=0 23 | char id=48 x=18 y=20 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 24 | char id=49 x=449 y=0 width=6 height=11 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 25 | char id=50 x=455 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 26 | char id=51 x=464 y=0 width=8 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 27 | char id=52 x=472 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 28 | char id=53 x=482 y=0 width=8 height=11 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 29 | char id=54 x=490 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 30 | char id=55 x=499 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 31 | char id=56 x=0 y=20 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 32 | char id=57 x=9 y=20 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 33 | char id=58 x=194 y=20 width=5 height=9 xoffset=-1 yoffset=4 xadvance=3 page=0 chnl=0 34 | char id=59 x=40 y=20 width=5 height=11 xoffset=-1 yoffset=4 xadvance=3 page=0 chnl=0 35 | char id=60 x=207 y=20 width=7 height=8 xoffset=0 yoffset=4 xadvance=7 page=0 chnl=0 36 | char id=61 x=242 y=20 width=7 height=6 xoffset=0 yoffset=4 xadvance=7 page=0 chnl=0 37 | char id=62 x=214 y=20 width=8 height=8 xoffset=0 yoffset=4 xadvance=7 page=0 chnl=0 38 | char id=63 x=32 y=20 width=8 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 39 | char id=64 x=59 y=0 width=13 height=14 xoffset=-1 yoffset=2 xadvance=12 page=0 chnl=0 40 | char id=65 x=192 y=0 width=11 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 41 | char id=66 x=203 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 42 | char id=67 x=212 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 43 | char id=68 x=222 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 44 | char id=69 x=231 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 45 | char id=70 x=240 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 46 | char id=71 x=249 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 47 | char id=72 x=259 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 48 | char id=73 x=269 y=0 width=4 height=11 xoffset=-1 yoffset=2 xadvance=3 page=0 chnl=0 49 | char id=74 x=273 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 50 | char id=75 x=282 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 51 | char id=76 x=292 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 52 | char id=77 x=301 y=0 width=12 height=11 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=0 53 | char id=78 x=313 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 54 | char id=79 x=323 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 55 | char id=80 x=333 y=0 width=9 height=11 xoffset=-1 yoffset=2 xadvance=7 page=0 chnl=0 56 | char id=81 x=81 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 57 | char id=82 x=342 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 58 | char id=83 x=352 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 59 | char id=84 x=362 y=0 width=9 height=11 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0 60 | char id=85 x=371 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=9 page=0 chnl=0 61 | char id=86 x=381 y=0 width=11 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 62 | char id=87 x=392 y=0 width=14 height=11 xoffset=-1 yoffset=2 xadvance=12 page=0 chnl=0 63 | char id=88 x=406 y=0 width=10 height=11 xoffset=-1 yoffset=2 xadvance=8 page=0 chnl=0 64 | char id=89 x=416 y=0 width=11 height=11 xoffset=-2 yoffset=2 xadvance=7 page=0 chnl=0 65 | char id=90 x=427 y=0 width=10 height=11 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0 66 | char id=91 x=28 y=0 width=6 height=15 xoffset=-1 yoffset=0 xadvance=3 page=0 chnl=0 67 | char id=92 x=184 y=0 width=8 height=12 xoffset=-1 yoffset=2 xadvance=5 page=0 chnl=0 68 | char id=93 x=34 y=0 width=5 height=15 xoffset=-2 yoffset=0 xadvance=3 page=0 chnl=0 69 | char id=94 x=231 y=20 width=7 height=7 xoffset=-1 yoffset=1 xadvance=5 page=0 chnl=0 70 | char id=95 x=285 y=20 width=8 height=3 xoffset=-1 yoffset=11 xadvance=6 page=0 chnl=0 71 | char id=96 x=268 y=20 width=6 height=4 xoffset=-1 yoffset=1 xadvance=4 page=0 chnl=0 72 | char id=97 x=84 y=20 width=9 height=9 xoffset=-1 yoffset=4 xadvance=7 page=0 chnl=0 73 | char id=98 x=96 y=0 width=9 height=12 xoffset=-1 yoffset=1 xadvance=8 page=0 chnl=0 74 | char id=99 x=93 y=20 width=9 height=9 xoffset=-1 yoffset=4 xadvance=7 page=0 chnl=0 75 | char id=100 x=105 y=0 width=9 height=12 xoffset=-1 yoffset=1 xadvance=8 page=0 chnl=0 76 | char id=101 x=102 y=20 width=9 height=9 xoffset=-1 yoffset=4 xadvance=7 page=0 chnl=0 77 | char id=102 x=114 y=0 width=7 height=12 xoffset=-1 yoffset=1 xadvance=5 page=0 chnl=0 78 | char id=103 x=121 y=0 width=9 height=12 xoffset=-1 yoffset=4 xadvance=8 page=0 chnl=0 79 | char id=104 x=130 y=0 width=8 height=12 xoffset=-1 yoffset=1 xadvance=7 page=0 chnl=0 80 | char id=105 x=437 y=0 width=5 height=11 xoffset=-1 yoffset=2 xadvance=3 page=0 chnl=0 81 | char id=106 x=53 y=0 width=6 height=14 xoffset=-2 yoffset=2 xadvance=3 page=0 chnl=0 82 | char id=107 x=138 y=0 width=9 height=12 xoffset=-1 yoffset=1 xadvance=7 page=0 chnl=0 83 | char id=108 x=147 y=0 width=4 height=12 xoffset=-1 yoffset=1 xadvance=3 page=0 chnl=0 84 | char id=109 x=111 y=20 width=12 height=9 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=0 85 | char id=110 x=123 y=20 width=8 height=9 xoffset=-1 yoffset=4 xadvance=7 page=0 chnl=0 86 | char id=111 x=131 y=20 width=9 height=9 xoffset=-1 yoffset=4 xadvance=8 page=0 chnl=0 87 | char id=112 x=151 y=0 width=9 height=12 xoffset=-1 yoffset=4 xadvance=8 page=0 chnl=0 88 | char id=113 x=160 y=0 width=9 height=12 xoffset=-1 yoffset=4 xadvance=8 page=0 chnl=0 89 | char id=114 x=77 y=20 width=7 height=10 xoffset=-1 yoffset=3 xadvance=4 page=0 chnl=0 90 | char id=115 x=140 y=20 width=8 height=9 xoffset=-1 yoffset=4 xadvance=7 page=0 chnl=0 91 | char id=116 x=442 y=0 width=7 height=11 xoffset=-2 yoffset=2 xadvance=4 page=0 chnl=0 92 | char id=117 x=148 y=20 width=8 height=9 xoffset=-1 yoffset=4 xadvance=7 page=0 chnl=0 93 | char id=118 x=156 y=20 width=9 height=9 xoffset=-1 yoffset=4 xadvance=6 page=0 chnl=0 94 | char id=119 x=165 y=20 width=12 height=9 xoffset=-1 yoffset=4 xadvance=10 page=0 chnl=0 95 | char id=120 x=177 y=20 width=9 height=9 xoffset=-1 yoffset=4 xadvance=6 page=0 chnl=0 96 | char id=121 x=169 y=0 width=8 height=12 xoffset=-1 yoffset=4 xadvance=6 page=0 chnl=0 97 | char id=122 x=186 y=20 width=8 height=9 xoffset=0 yoffset=4 xadvance=7 page=0 chnl=0 98 | char id=123 x=39 y=0 width=7 height=15 xoffset=-1 yoffset=1 xadvance=4 page=0 chnl=0 99 | char id=124 x=92 y=0 width=4 height=13 xoffset=-1 yoffset=2 xadvance=3 page=0 chnl=0 100 | char id=125 x=46 y=0 width=7 height=15 xoffset=-1 yoffset=1 xadvance=5 page=0 chnl=0 101 | char id=126 x=259 y=20 width=9 height=5 xoffset=0 yoffset=5 xadvance=9 page=0 chnl=0 102 | char id=127 x=0 y=0 width=15 height=20 xoffset=-1 yoffset=-3 xadvance=13 page=0 chnl=0 103 | kernings count=82 104 | kerning first=86 second=44 amount=-1 105 | kerning first=84 second=109 amount=-1 106 | kerning first=76 second=119 amount=-1 107 | kerning first=104 second=39 amount=-1 108 | kerning first=70 second=44 amount=-1 109 | kerning first=84 second=44 amount=-1 110 | kerning first=89 second=65 amount=-1 111 | kerning first=84 second=45 amount=-1 112 | kerning first=84 second=46 amount=-1 113 | kerning first=65 second=89 amount=-1 114 | kerning first=76 second=39 amount=-2 115 | kerning first=118 second=44 amount=-1 116 | kerning first=84 second=65 amount=-1 117 | kerning first=87 second=44 amount=-1 118 | kerning first=89 second=85 amount=-1 119 | kerning first=87 second=46 amount=-1 120 | kerning first=84 second=101 amount=-1 121 | kerning first=34 second=65 amount=-1 122 | kerning first=109 second=39 amount=-1 123 | kerning first=110 second=34 amount=-1 124 | kerning first=76 second=121 amount=-1 125 | kerning first=34 second=39 amount=-1 126 | kerning first=39 second=115 amount=-1 127 | kerning first=84 second=97 amount=-1 128 | kerning first=79 second=46 amount=-1 129 | kerning first=65 second=84 amount=-1 130 | kerning first=80 second=65 amount=-1 131 | kerning first=89 second=44 amount=-1 132 | kerning first=39 second=34 amount=-1 133 | kerning first=111 second=34 amount=-1 134 | kerning first=68 second=46 amount=-1 135 | kerning first=76 second=84 amount=-2 136 | kerning first=65 second=39 amount=-1 137 | kerning first=84 second=110 amount=-1 138 | kerning first=114 second=44 amount=-1 139 | kerning first=84 second=115 amount=-1 140 | kerning first=89 second=74 amount=-1 141 | kerning first=118 second=46 amount=-1 142 | kerning first=39 second=65 amount=-1 143 | kerning first=76 second=89 amount=-2 144 | kerning first=47 second=47 amount=-1 145 | kerning first=111 second=39 amount=-1 146 | kerning first=121 second=46 amount=-1 147 | kerning first=70 second=74 amount=-2 148 | kerning first=70 second=46 amount=-1 149 | kerning first=110 second=39 amount=-1 150 | kerning first=34 second=34 amount=-1 151 | kerning first=76 second=86 amount=-1 152 | kerning first=84 second=113 amount=-1 153 | kerning first=84 second=111 amount=-1 154 | kerning first=109 second=34 amount=-1 155 | kerning first=34 second=115 amount=-1 156 | kerning first=80 second=44 amount=-2 157 | kerning first=70 second=65 amount=-1 158 | kerning first=76 second=87 amount=-1 159 | kerning first=82 second=84 amount=-1 160 | kerning first=80 second=46 amount=-2 161 | kerning first=119 second=44 amount=-1 162 | kerning first=121 second=44 amount=-1 163 | kerning first=44 second=39 amount=-1 164 | kerning first=84 second=74 amount=-2 165 | kerning first=104 second=34 amount=-1 166 | kerning first=39 second=39 amount=-1 167 | kerning first=119 second=46 amount=-1 168 | kerning first=79 second=44 amount=-1 169 | kerning first=84 second=112 amount=-1 170 | kerning first=65 second=86 amount=-1 171 | kerning first=68 second=44 amount=-1 172 | kerning first=46 second=34 amount=-1 173 | kerning first=84 second=99 amount=-1 174 | kerning first=84 second=117 amount=-1 175 | kerning first=86 second=46 amount=-1 176 | kerning first=84 second=100 amount=-1 177 | kerning first=44 second=34 amount=-1 178 | kerning first=76 second=34 amount=-2 179 | kerning first=76 second=118 amount=-1 180 | kerning first=89 second=46 amount=-1 181 | kerning first=80 second=74 amount=-1 182 | kerning first=114 second=46 amount=-1 183 | kerning first=84 second=103 amount=-1 184 | kerning first=46 second=39 amount=-1 185 | kerning first=65 second=34 amount=-1 186 | -------------------------------------------------------------------------------- /assets/roboto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewaterlabs/vimtris/f339e0102a26890dd3e65f3285b94eb0172456a1/assets/roboto.png -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vimtris", 3 | "sources": "src", 4 | "bs-dependencies": ["ReasonglInterface", "ReasonglWeb", "ReasonglNative", "rainbow-ui"], 5 | "bs-super-errors": true, 6 | "entries": [ 7 | {"backend": "bytecode", "main-module": "IndexHot"}, 8 | {"backend": "native", "main-module": "Index"}, 9 | {"backend": "js", "main-module": "Index"} 10 | ], 11 | "refmt": 3 12 | } 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vimtris 5 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vimtris", 3 | "version": "0.0.0", 4 | "description": "A game in reasonml, tetris using vim keys", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "./lib/bs/bytecode/indexhot.byte", 8 | "start:native": "./lib/bs/native/index.native", 9 | "build": "bsb -make-world", 10 | "build:web": "bsb -make-world -backend js", 11 | "build:native": "bsb -make-world -backend native", 12 | "clean": "bsb -clean-world", 13 | "webpack": "webpack" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+github.com/codewaterlabs/vimtris" 18 | }, 19 | "keywords": [], 20 | "author": "Gudmund Vatn", 21 | "license": "MIT", 22 | "dependencies": { 23 | "rainbow-ui": "git+https://github.com/codewaterlabs/rainbow-ui#a072a7b18c84e192f2f28bbed62441641aa45b41" 24 | }, 25 | "devDependencies": { 26 | "uglifyjs-webpack-plugin": "^1.2.2", 27 | "webpack": "^4.1.0", 28 | "webpack-cli": "^2.0.10" 29 | } 30 | } -------------------------------------------------------------------------------- /require_polyfill.js: -------------------------------------------------------------------------------- 1 | function normalizeArray(parts, allowAboveRoot) { 2 | // if the path tries to go above the root, `up` ends up > 0 3 | var up = 0; 4 | for (var i = parts.length - 1; i >= 0; i--) { 5 | var last = parts[i]; 6 | if (last === '.') { 7 | parts.splice(i, 1); 8 | } else if (last === '..') { 9 | parts.splice(i, 1); 10 | up++; 11 | } else if (up) { 12 | parts.splice(i, 1); 13 | up--; 14 | } 15 | } 16 | 17 | // if the path is allowed to go above the root, restore leading ..s 18 | if (allowAboveRoot) { 19 | for (; up--; up) { 20 | parts.unshift('..'); 21 | } 22 | } 23 | 24 | return parts; 25 | }; 26 | 27 | function pathNormalize(path) { 28 | var isAbsolute = path.charAt(0) === '/'; 29 | var trailingSlash = path.substr(-1) === '/'; 30 | 31 | // Normalize the path 32 | path = normalizeArray(path.split('/').filter(function(p) { 33 | return !!p; 34 | }), !isAbsolute).join('/'); 35 | 36 | if (!path && !isAbsolute) { 37 | path = '.'; 38 | } 39 | if (path && trailingSlash) { 40 | path += '/'; 41 | } 42 | 43 | return (isAbsolute ? '/' : '') + path; 44 | }; 45 | 46 | var globalEval = eval; 47 | var currentScript = document.currentScript; 48 | var projectRoot = currentScript.dataset['project-root'] || currentScript.dataset['projectRoot']; 49 | if (projectRoot == null) { 50 | throw new Error('The attribute `data-project-root` isn\'t found in the script tag. You need to provide the root (in which node_modules reside).') 51 | } 52 | var nodeModulesDir = projectRoot + '/node_modules/'; 53 | 54 | var modulesCache = {}; 55 | var packageJsonMainCache = {}; 56 | 57 | var ensureEndsWithJs = function(path) { 58 | if (path.endsWith('.js')) { 59 | return path; 60 | } else { 61 | return path + '.js'; 62 | } 63 | }; 64 | function loadScript(scriptPath) { 65 | var request = new XMLHttpRequest(); 66 | 67 | request.open("GET", scriptPath, false); // sync 68 | request.send(); 69 | var dirSeparatorIndex = scriptPath.lastIndexOf('/'); 70 | var dir = dirSeparatorIndex === -1 ? '.' : scriptPath.slice(0, dirSeparatorIndex); 71 | 72 | var moduleText = ` 73 | (function(module, exports, modulesCache, packageJsonMainCache, nodeModulesDir) { 74 | function require(path) { 75 | var __dirname = "${dir}/"; 76 | var resolvedPath; 77 | if (path.startsWith('.')) { 78 | // require('./foo/bar') 79 | resolvedPath = ensureEndsWithJs(__dirname + path); 80 | } else if (path.indexOf('/') === -1) { 81 | // require('react') 82 | var packageJson = pathNormalize(nodeModulesDir + path + '/package.json'); 83 | if (packageJsonMainCache[packageJson] == null) { 84 | var jsonRequest = new XMLHttpRequest(); 85 | jsonRequest.open("GET", packageJson, false); 86 | jsonRequest.send(); 87 | var main; 88 | if (jsonRequest.responseText != null) { 89 | main = JSON.parse(jsonRequest.responseText).main; 90 | }; 91 | if (main == null) { 92 | main = 'index.js'; 93 | } else if (!main.endsWith('.js')) { 94 | main = main + '.js'; 95 | } 96 | packageJsonMainCache[packageJson] = nodeModulesDir + path + '/' + main; 97 | } 98 | resolvedPath = packageJsonMainCache[packageJson]; 99 | } else { 100 | // require('react/bar') 101 | resolvedPath = ensureEndsWithJs(nodeModulesDir + path); 102 | }; 103 | resolvedPath = pathNormalize(resolvedPath); 104 | if (modulesCache[resolvedPath] != null) { 105 | return modulesCache[resolvedPath]; 106 | }; 107 | var result = loadScript(resolvedPath); 108 | modulesCache[resolvedPath] = result; 109 | return result; 110 | }; 111 | var process = {env: {}, argv: []}; 112 | var global = {}; 113 | 114 | 115 | // -------Begin Require Polyfilled Module Loaded From Disk------------------------------ 116 | // file: ${scriptPath} 117 | // root: ${projectRoot} 118 | // ---------------------------------------------------------------------- 119 | ${request.responseText} 120 | // -------End Polyfill Loaded From Disk------------------------------ 121 | // file: ${scriptPath} 122 | // root: ${projectRoot} 123 | // ---------------------------------------------------------------------- 124 | return module.exports})\n//@ sourceURL=${scriptPath}`; 125 | var module = {exports: {}}; 126 | return globalEval(moduleText)(module, module.exports, modulesCache, packageJsonMainCache, nodeModulesDir); 127 | }; 128 | 129 | loadScript(currentScript.dataset.main) 130 | -------------------------------------------------------------------------------- /src/background.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform vec2 pixelSize; 5 | varying vec2 vPosition; 6 | varying vec2 pixelPos; 7 | void main() { 8 | float aspect = pixelSize.x / pixelSize.y; 9 | vPosition = (aspect > 1.0) ? vec2(position.x, position.y / aspect) : vec2(position.x * aspect, position.y); 10 | vPosition = vec2(position.x * aspect, position.y); 11 | // Normalize to 0.0 to 1.0 12 | //vPosition = vec2((vPosition.x + 1.0) * 0.5, (vPosition.y + 1.0) * 0.5); 13 | pixelPos = (position + vec2(1.0, -1.0)) * vec2(0.5, -0.5) * pixelSize; 14 | gl_Position = vec4(position, 0.0, 1.0); 15 | } 16 | |}; 17 | 18 | /* http://alex-charlton.com/posts/Dithering_on_the_GPU/ */ 19 | let fragmentSource = {| 20 | precision mediump float; 21 | uniform float anim; 22 | uniform vec3 color; 23 | varying vec2 vPosition; 24 | varying vec2 pixelPos; 25 | 26 | const mat4 indexMat = mat4( 27 | 0.0, 8.0, 2.0, 10.0, 28 | 12.0, 4.0, 14.0, 6.0, 29 | 3.0, 11.0, 1.0, 9.0, 30 | 15.0, 7.0, 13.0, 5.0 31 | ); 32 | 33 | const float colorInterval = 0.04; 34 | 35 | float indexVal() { 36 | int xMod = int(mod(pixelPos.x * 0.5, 4.0)); 37 | int yMod = int(mod(pixelPos.y * 0.5, 4.0)); 38 | float idxVal = 39 | (xMod == 0) ? 40 | (yMod == 0) ? indexMat[0][0] 41 | : (yMod == 1) ? indexMat[0][1] 42 | : (yMod == 2) ? indexMat[0][2] 43 | : indexMat[0][3] 44 | : (xMod == 1) ? 45 | (yMod == 0) ? indexMat[1][0] 46 | : (yMod == 1) ? indexMat[1][1] 47 | : (yMod == 2) ? indexMat[1][2] 48 | : indexMat[1][3] 49 | : (xMod == 2) ? 50 | (yMod == 0) ? indexMat[2][0] 51 | : (yMod == 1) ? indexMat[2][1] 52 | : (yMod == 2) ? indexMat[2][2] 53 | : indexMat[2][3] 54 | : 55 | (yMod == 0) ? indexMat[3][0] 56 | : (yMod == 1) ? indexMat[3][1] 57 | : (yMod == 2) ? indexMat[3][2] 58 | : indexMat[3][3]; 59 | return idxVal / 16.0; 60 | } 61 | 62 | void main() { 63 | // Index value for this pixel 64 | float idxVal = indexVal(); 65 | vec2 lightPos = vec2(0.0, 0.75); 66 | float dist = distance(lightPos, vPosition * vec2(1.0, 1.6)) / 2.5; 67 | // Distance in increasing power outwards 68 | float colorCoef = max(1.0 - (pow(4.0, dist) - 1.0) / 6.0, 0.0); 69 | float cMod = mod(colorCoef, colorInterval); 70 | float diffDown = cMod / colorInterval; 71 | bool isClosestDown = (diffDown < 0.5) ? true : false; 72 | float diff = (isClosestDown) ? diffDown : (1.0 - diffDown); 73 | bool useClosest = (diff < idxVal); 74 | float closestDown = colorCoef - cMod; 75 | float closestUp = colorCoef + colorInterval - cMod; 76 | colorCoef = useClosest ? 77 | (isClosestDown ? closestDown : closestUp) 78 | : (isClosestDown ? closestUp : closestDown); 79 | vec3 c = mix(vec3(0.0, 0.0, 0.0), color, colorCoef); 80 | gl_FragColor = vec4(c * anim, 1.0); 81 | } 82 | |}; 83 | 84 | open Gpu; 85 | 86 | let makeNode = (color, children) => 87 | Scene.( 88 | Scene.makeNode( 89 | ~key="background", 90 | ~vertShader=Shader.make(vertexSource), 91 | ~fragShader=Shader.make(fragmentSource), 92 | ~uniforms=[("color", color), ("anim", UFloat.make(0.0))], 93 | ~pixelSizeUniform=true, 94 | ~size=Dimensions(Scale(1.0), Scale(1.0)), 95 | ~padding=Scale(0.05), 96 | ~vAlign=Scene.AlignMiddle, 97 | ~children, 98 | () 99 | ) 100 | ); 101 | -------------------------------------------------------------------------------- /src/config.re: -------------------------------------------------------------------------------- 1 | let tickDuration = 0.5; 2 | 3 | let tileCols = 12; 4 | 5 | let tileRows = 26; 6 | 7 | let blinkTime = 0.7; 8 | 9 | let dropAnimDuration = 0.8; 10 | 11 | let dropDownBeforeTouchDown = 0.15; 12 | 13 | let dropDownBeforeTick = 0.5; 14 | 15 | let pointLight = Data.Vec3.make(0.0, -0.4, 2.0); 16 | 17 | let camera = Data.Vec3.make(0.0, 0.4, 4.0); 18 | -------------------------------------------------------------------------------- /src/currEl.re: -------------------------------------------------------------------------------- 1 | let currElVertex = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 translation; 5 | uniform mat3 layout; 6 | uniform mat3 sdfTilesMat; 7 | varying vec2 vPosition; 8 | varying vec2 vTexPos; 9 | void main() { 10 | vPosition = (vec3(position, 1.0) * translation).xy; 11 | vec3 transformed = vec3(vPosition, 1.0) * layout; 12 | vTexPos = (vec3(vPosition, 1.0) * sdfTilesMat).xy; 13 | gl_Position = vec4(transformed.xy, 0.0, 1.0); 14 | } 15 | |}; 16 | 17 | let currElFragment = {| 18 | precision mediump float; 19 | varying vec2 vPosition; 20 | varying vec2 vTexPos; 21 | 22 | uniform vec3 elColor; 23 | uniform sampler2D sdfTiles; 24 | 25 | void main() { 26 | vec3 sdfColor = texture2D(sdfTiles, vTexPos).xyz; 27 | gl_FragColor = vec4(mix(elColor, sdfColor, 0.4), 1.0); 28 | } 29 | |}; 30 | 31 | open Gpu; 32 | 33 | let makeNode = (elState: SceneState.elState, sdfTiles) => 34 | Scene.makeNode( 35 | ~key="currEl", 36 | ~vertShader=Shader.make(currElVertex), 37 | ~fragShader=Shader.make(currElFragment), 38 | ~vo=elState.vo, 39 | ~partialDraw=true, 40 | ~uniforms=[("elColor", elState.color), ("translation", elState.pos)], 41 | ~textures=[("sdfTiles", Scene.SceneTex.node(sdfTiles))], 42 | () 43 | ); 44 | -------------------------------------------------------------------------------- /src/drawElement.re: -------------------------------------------------------------------------------- 1 | let lightBaseVert = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | uniform mat3 model; 6 | void main() { 7 | vec2 pos = (vec3(position, 1.0) * model * layout).xy; 8 | pos = pos + vec2(0.01, 0.02); 9 | gl_Position = vec4(pos.xy, 0.0, 1.0); 10 | } 11 | |}; 12 | 13 | let lightBaseFrag = {| 14 | precision mediump float; 15 | 16 | void main() { 17 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); 18 | } 19 | |}; 20 | 21 | let vertexSource = {| 22 | precision mediump float; 23 | attribute vec2 position; 24 | uniform mat3 layout; 25 | uniform mat3 lightMat; 26 | varying vec2 lightUV; 27 | uniform mat3 elMat; 28 | varying vec2 elUV; 29 | void main() { 30 | lightUV = (vec3(position, 1.0) * lightMat).xy; 31 | elUV = (vec3(position, 1.0) * elMat).xy; 32 | vec2 pos = (vec3(position, 1.0) * layout).xy; 33 | gl_Position = vec4(pos, 0.0, 1.0); 34 | } 35 | |}; 36 | 37 | let fragmentSource = {| 38 | precision mediump float; 39 | varying vec2 lightUV; 40 | varying vec2 elUV; 41 | uniform sampler2D light; 42 | uniform sampler2D el; 43 | uniform vec3 color; 44 | 45 | void main() { 46 | vec4 elColor = texture2D(el, elUV); 47 | vec4 light = texture2D(light, lightUV); 48 | gl_FragColor = mix(vec4(0.0, 0.0, 0.0, light.x * 0.8), elColor, step(0.01, elColor.a)); 49 | } 50 | |}; 51 | 52 | let lightBaseProgram: ref(option(Scene.sceneProgram)) = ref(None); 53 | 54 | let drawElementProgram: ref(option(Scene.sceneProgram)) = ref(None); 55 | 56 | let getLightBaseProgram = () => 57 | switch lightBaseProgram^ { 58 | | Some(lightBaseProgram) => lightBaseProgram 59 | | None => 60 | let program = 61 | Scene.makeProgram( 62 | ~vertShader=Gpu.Shader.make(lightBaseVert), 63 | ~fragShader=Gpu.Shader.make(lightBaseFrag), 64 | ~requiredUniforms=[("model", Gpu.GlType.Mat3f)], 65 | ~attribs=[Gpu.VertexAttrib.make("position", Vec2f)], 66 | () 67 | ); 68 | lightBaseProgram := Some(program); 69 | program; 70 | }; 71 | 72 | let getDrawElementProgram = () => 73 | switch drawElementProgram^ { 74 | | Some(drawElementProgram) => drawElementProgram 75 | | None => 76 | let program = 77 | Scene.makeProgram( 78 | ~vertShader=Gpu.Shader.make(vertexSource), 79 | ~fragShader=Gpu.Shader.make(fragmentSource), 80 | ~requiredUniforms=[("color", Gpu.GlType.Vec3f)], 81 | ~attribs=[Gpu.VertexAttrib.make("position", Vec2f)], 82 | ~requiredTextures=[("light", true), ("el", true)], 83 | () 84 | ); 85 | drawElementProgram := Some(program); 86 | program; 87 | }; 88 | 89 | let makeNode = (elState: SceneState.elState, lighting) => { 90 | let cols = 2.0; 91 | let rows = 1.5; 92 | let margin = Scene.MarginXY(Scale(0.22), Scale(0.0)); 93 | let toTex = Gpu.Texture.makeEmptyRgb(); 94 | let tempTex = Gpu.Texture.makeEmptyRgb(); 95 | let size = Scene.Aspect(cols /. rows); 96 | let lightBaseNode = 97 | Scene.makeNode( 98 | ~cls="lightBase", 99 | ~program=getLightBaseProgram(), 100 | ~uniforms=[("model", elState.pos)], 101 | ~vo=elState.vo, 102 | ~partialDraw=true, 103 | ~drawTo=Scene.TextureItem(toTex), 104 | ~clearOnDraw=true, 105 | () 106 | ); 107 | let lightNode = Blur2.makeNode(lightBaseNode, toTex, tempTex, 10.0, 10.0); 108 | let elNode = 109 | SdfTiles.makeNode( 110 | cols, 111 | rows, 112 | lighting, 113 | ~vo=elState.vo, 114 | ~color=SdfNode.SdfDynColor(elState.color), 115 | ~model=elState.pos, 116 | ~tileSpace=0.25, 117 | ~drawTo=Scene.TextureRGBA, 118 | () 119 | ); 120 | /*DrawTex.makeNode(lightNode, ())*/ 121 | Scene.makeNode( 122 | ~cls="drawElement", 123 | ~program=getDrawElementProgram(), 124 | ~transparent=true, 125 | ~size, 126 | ~margin, 127 | ~uniforms=[("color", elState.color)], 128 | ~deps=[lightNode, elNode], 129 | ~textures=[ 130 | ("light", Scene.SceneTex.node(lightNode)), 131 | ("el", Scene.SceneTex.node(elNode)) 132 | ], 133 | () 134 | ); 135 | }; 136 | /* 137 | Scene.makeNode( 138 | ~cls="element", 139 | ~size=Aspect(4.0 /. 3.0), 140 | ~partialDraw=true, 141 | ~margin=MarginXY(Scale(0.25), Scale(0.022)), 142 | ~vertShader=Shader.make(currElVertex), 143 | ~fragShader=Shader.make(currElFragment), 144 | ~vo=elState.vo, 145 | ~uniforms=[ 146 | ("elColor", elState.color), 147 | ("translation", elState.pos) 148 | ], 149 | () 150 | )*/ 151 | -------------------------------------------------------------------------------- /src/dropBeams.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | attribute float fromDrop; 5 | uniform mat3 layout; 6 | varying float vFromDrop; 7 | void main() { 8 | vFromDrop = fromDrop; 9 | vec2 pos = (vec3(position, 1.0) * layout).xy; 10 | gl_Position = vec4(pos, 0.0, 1.0); 11 | } 12 | |}; 13 | 14 | let fragmentSource = {| 15 | precision mediump float; 16 | varying float vFromDrop; 17 | uniform float sinceDrop; 18 | 19 | void main() { 20 | float c = max(0.0, vFromDrop - sinceDrop); 21 | gl_FragColor = vec4(c, c, c, 1.0); 22 | } 23 | |}; 24 | 25 | open Gpu; 26 | 27 | let makeNode = vo => 28 | Scene.makeNode( 29 | ~key="dropBeams", 30 | ~vertShader=Shader.make(vertexSource), 31 | ~fragShader=Shader.make(fragmentSource), 32 | ~uniforms=[("sinceDrop", Scene.UFloat.zero())], 33 | ~drawTo=Scene.TextureRGBDim(1024), 34 | ~clearOnDraw=true, 35 | ~vo, 36 | () 37 | ); 38 | -------------------------------------------------------------------------------- /src/game.re: -------------------------------------------------------------------------------- 1 | module Document = { 2 | type window; 3 | let window: window = [%bs.raw "window"]; 4 | [@bs.send] 5 | external addEventListener : ('window, string, 'eventT => unit) => unit = 6 | "addEventListener"; 7 | }; 8 | 9 | [@bs.set] external setLastKeyCode : ('a, string) => unit = "__lastKeyCode"; 10 | 11 | [@bs.get] external lastKeyCode : 'a => string = "__lastKeyCode"; 12 | 13 | [@bs.get] external getKeyEventKey : 'eventT => string = "key"; 14 | 15 | open Config; 16 | 17 | type element = 18 | | Cube 19 | | Line 20 | | Triangle 21 | | RightTurn 22 | | LeftTurn 23 | | LeftL 24 | | RightL; 25 | 26 | type gameState = 27 | | StartScreen 28 | | StartHelp 29 | | HelpScreen 30 | | Running 31 | | Paused 32 | | NextLevel 33 | | GameOver; 34 | 35 | type startScreenAction = 36 | | StartGame 37 | | Help 38 | | NoAction; 39 | 40 | type gameAction = 41 | | MoveLeft 42 | | MoveRight 43 | | MoveDown 44 | | BlockLeft 45 | | BlockRight 46 | | BlockEnd 47 | | CancelDown 48 | | DropDown 49 | | RotateCW 50 | | RotateCCW 51 | | HoldElement 52 | | MoveBeginning 53 | | MoveEnd 54 | | Pause 55 | | Help 56 | | NoAction; 57 | 58 | type nextLevelAction = 59 | | NextLevel 60 | | NoAction; 61 | 62 | type gameOverAction = 63 | | NewGame 64 | | Help 65 | | NoAction; 66 | 67 | type pauseAction = 68 | | Resume 69 | | Help 70 | | NoAction; 71 | 72 | type helpScreenAction = 73 | | Resume 74 | | NoAction; 75 | 76 | type inputAction = 77 | | GameAction(gameAction) 78 | | NextLevelAction(nextLevelAction) 79 | | GameOverAction(gameOverAction) 80 | | PauseAction(pauseAction) 81 | | StartScreenAction(startScreenAction) 82 | | HelpScreenAction(helpScreenAction); 83 | 84 | let getTetronimo = element => 85 | switch element { 86 | | Cube => Tetronimo.cubeTiles 87 | | Line => Tetronimo.lineTiles 88 | | Triangle => Tetronimo.triangleTiles 89 | | RightTurn => Tetronimo.rightTurnTiles 90 | | LeftTurn => Tetronimo.leftTurnTiles 91 | | LeftL => Tetronimo.leftLTiles 92 | | RightL => Tetronimo.rightLTiles 93 | }; 94 | 95 | let elTiles = (element, rotation) => { 96 | let tetronimo = getTetronimo(element); 97 | switch rotation { 98 | | 1 => tetronimo.points90 99 | | 2 => tetronimo.points180 100 | | 3 => tetronimo.points270 101 | | _ => tetronimo.points 102 | }; 103 | }; 104 | 105 | let colors = 106 | Array.map( 107 | color => 108 | Color.fromArray( 109 | Array.map(component => float_of_int(component) /. 255.0, color) 110 | ), 111 | [| 112 | [|199, 214, 240|], /* Standard unfilled color */ 113 | [|205, 220, 246|], /* Standard lighter color */ 114 | [|130, 240, 250|], /* Magenta line */ 115 | [|120, 130, 250|], /* Blue left L */ 116 | [|250, 210, 80|], /* Orange right L */ 117 | [|250, 250, 130|], /* Yellow cube */ 118 | [|140, 250, 140|], /* Green right shift */ 119 | [|180, 100, 230|], /* Purple triangle */ 120 | [|240, 130, 120|] /* Red left shift */ 121 | |] 122 | ); 123 | 124 | /* Temp, adjust colors */ 125 | let colors = 126 | Array.map( 127 | color => { 128 | let hsl = Color.Hsl.fromRgb(color); 129 | Color.Hsl.incrL(hsl, 0.07); 130 | Color.Hsl.incrS(hsl, 0.07); 131 | Color.Hsl.toRgb(hsl); 132 | }, 133 | colors 134 | ); 135 | 136 | /* Center position and radius 137 | for each tetronimo in each rotation 138 | Can add other aggregate element data */ 139 | type posRadius = { 140 | centerX: float, 141 | centerY: float, 142 | radiusX: float, 143 | radiusY: float, 144 | width: int, 145 | height: int, 146 | offsetX: int 147 | }; 148 | 149 | let centerRadius = Hashtbl.create(7 * 4); 150 | 151 | let addCenterRadius = (el, rot) => { 152 | let left = ref(10); 153 | let right = ref(-10); 154 | let top = ref(10); 155 | let bottom = ref(-10); 156 | List.iter( 157 | ((x, y)) => { 158 | if (left^ > x) { 159 | left := x; 160 | }; 161 | if (right^ < x + 1) { 162 | right := x + 1; 163 | }; 164 | /* Y is flipped from definition in tetronimo.re and canvas, 165 | some accidentality. Maybe generally use gl coord system 166 | possibly clean up */ 167 | if (top^ > y) { 168 | top := y; 169 | }; 170 | if (bottom^ < y + 1) { 171 | bottom := y + 1; 172 | }; 173 | }, 174 | elTiles(el, rot) 175 | ); 176 | let width = right^ - left^; 177 | let height = top^ - bottom^; 178 | let radiusX = float_of_int(width); 179 | /*let centerX = float_of_int(left^) +. radiusX /. 2.0;*/ 180 | let radiusY = float_of_int(height) *. (-1.0); 181 | /*let centerY = float_of_int(top^ * (-1)) -. radiusY /. 2.0;*/ 182 | let (centerX, centerY) = { 183 | /* Center positions of width, height */ 184 | let y = float_of_int(top^ * (-1)) -. radiusY /. 2.0; 185 | let x = float_of_int(left^) +. radiusX /. 2.0; 186 | switch (el, rot) { 187 | | (LeftTurn, 0) 188 | | (LeftTurn, 2) => (x, y +. 0.5) 189 | | (LeftTurn, 1) 190 | | (LeftTurn, 3) => (x +. 0.5, y +. 1.0) 191 | | (RightTurn, 0) 192 | | (RightTurn, 2) => (x, y +. 0.5) 193 | | (RightTurn, 1) 194 | | (RightTurn, 3) => (x -. 0.5, y +. 1.0) 195 | | (Triangle, 2) => (x, y +. 0.5) 196 | | (Triangle, 1) => (x -. 0.25, y +. 1.5) 197 | | (Triangle, 3) => (x +. 0.25, y +. 1.5) 198 | | (Cube, _) => (x, y +. 1.0) 199 | | (LeftL, 2) => (x, y +. 0.5) 200 | | (LeftL, 1) => (x, y +. 1.5) 201 | | (LeftL, 3) => (x +. 0.5, y +. 1.5) 202 | | (RightL, 2) => (x, y +. 0.5) 203 | | (RightL, 1) => (x -. 0.5, y +. 1.5) 204 | | (RightL, 3) => (x +. 0.25, y +. 1.5) 205 | | (Line, 1) 206 | | (Line, 3) => (x, y +. 2.5) 207 | | _ => (x, y) 208 | }; 209 | }; 210 | let offsetX = left^; 211 | Hashtbl.add( 212 | centerRadius, 213 | (el, rot), 214 | {centerX, centerY, radiusX, radiusY, width, height, offsetX} 215 | ); 216 | }; 217 | 218 | List.iter( 219 | el => { 220 | addCenterRadius(el, 0); 221 | addCenterRadius(el, 1); 222 | addCenterRadius(el, 2); 223 | addCenterRadius(el, 3); 224 | }, 225 | [Cube, Line, Triangle, RightTurn, LeftTurn, LeftL, RightL] 226 | ); 227 | 228 | type elData = { 229 | el: element, 230 | posX: int, 231 | posY: int, 232 | color: int, 233 | rotation: int 234 | }; 235 | 236 | let beamNone = (-2); 237 | 238 | module TouchDown = { 239 | type touchDownState = 240 | | TouchDownInit 241 | | DropOnly 242 | | Blinking 243 | | Done; 244 | type t = { 245 | state: touchDownState, 246 | drawn: bool, 247 | /* Array of completed rows */ 248 | rows: array(int), 249 | completedRows: bool, 250 | elapsed: float, 251 | isDropDown: bool 252 | }; 253 | let make = () => { 254 | state: TouchDownInit, 255 | drawn: false, 256 | rows: [||], 257 | completedRows: false, 258 | elapsed: 0.0, 259 | isDropDown: false 260 | }; 261 | }; 262 | 263 | module ElQueue = { 264 | type t = Queue.t(elData); 265 | let randomEl = () => { 266 | let elType = 267 | switch (Random.int(7)) { 268 | | 0 => Cube 269 | | 1 => Line 270 | | 2 => Triangle 271 | | 3 => RightTurn 272 | | 4 => LeftTurn 273 | | 5 => LeftL 274 | | 6 => RightL 275 | | _ => Cube 276 | }; 277 | let tetronimo = getTetronimo(elType); 278 | /* Positions needs to work with display of next element */ 279 | let (posX, posY) = 280 | switch elType { 281 | | Cube => (4, 1) 282 | | Line => (5, 2) 283 | | Triangle => (3, 3) 284 | | RightTurn => (3, 3) 285 | | LeftTurn => (3, 3) 286 | | LeftL => (3, 3) 287 | | RightL => (3, 3) 288 | }; 289 | {el: elType, color: tetronimo.colorIndex, rotation: 0, posX, posY}; 290 | }; 291 | let setHoldPos = elData => { 292 | let (posX, posY) = 293 | switch elData.el { 294 | | Cube => (4, 1) 295 | | Line => (5, 2) 296 | | Triangle => (3, 3) 297 | | RightTurn => (3, 3) 298 | | LeftTurn => (3, 3) 299 | | LeftL => (3, 3) 300 | | RightL => (3, 3) 301 | }; 302 | {...elData, rotation: elData.rotation, posX, posY}; 303 | }; 304 | let initQueue = queue => { 305 | Queue.clear(queue); 306 | Queue.push(randomEl(), queue); 307 | Queue.push(randomEl(), queue); 308 | Queue.push(randomEl(), queue); 309 | }; 310 | let make = () : t => { 311 | let q = Queue.create(); 312 | initQueue(q); 313 | q; 314 | }; 315 | let setBoardInitPos = elData => { 316 | let middleX = tileCols / 2; 317 | let (posX, posY) = 318 | switch elData.el { 319 | | Cube => (middleX + 1, 3) 320 | | Line => (middleX + 1, 3) 321 | | Triangle => (middleX, 4) 322 | | RightTurn => (middleX, 4) 323 | | LeftTurn => (middleX, 4) 324 | | LeftL => (middleX, 4) 325 | | RightL => (middleX, 4) 326 | }; 327 | {...elData, posX, posY}; 328 | }; 329 | /* Use nextEl(state) to account for holding element */ 330 | let pop = queue => { 331 | Queue.push(randomEl(), queue); 332 | Queue.pop(queue); 333 | }; 334 | }; 335 | 336 | type stateT = { 337 | gameState, 338 | action: inputAction, 339 | curEl: elData, 340 | holdingEl: option(elData), 341 | elMoved: bool, 342 | elChanged: bool, 343 | hasDroppedDown: bool, 344 | lastTick: float, 345 | curTime: float, 346 | tiles: array(array(int)), 347 | completedRows: int, 348 | sceneTiles: array(int), 349 | updateTiles: bool, 350 | beams: array((int, int)), 351 | dropBeams: array((int, int)), 352 | dropColor: Color.t, 353 | paused: bool, 354 | touchDown: option(TouchDown.t), 355 | elQueue: ElQueue.t, 356 | deltaTime: float 357 | }; 358 | 359 | let nextEl = state => { 360 | let next = ElQueue.pop(state.elQueue); 361 | { 362 | ...state, 363 | elChanged: true, 364 | elMoved: true, 365 | lastTick: state.curTime, 366 | curEl: ElQueue.setBoardInitPos(next) 367 | }; 368 | }; 369 | 370 | let updateBeams = state => { 371 | /* Reset element tile rows */ 372 | Array.iteri( 373 | (i, (tileRow, _toRow)) => 374 | if (tileRow > beamNone) { 375 | state.beams[i] = (beamNone, 0); 376 | }, 377 | state.beams 378 | ); 379 | /* Set row where element tile is */ 380 | List.iter( 381 | ((x, y)) => { 382 | let pointX = state.curEl.posX + x; 383 | let pointY = state.curEl.posY + y; 384 | let (beamFrom, beamTo) = state.beams[pointX]; 385 | if (beamFrom == beamNone) { 386 | state.beams[pointX] = (pointY, beamTo); 387 | }; 388 | }, 389 | elTiles(state.curEl.el, state.curEl.rotation) 390 | ); 391 | /* Set end of beam */ 392 | /* This could almost be cached, but there are edge cases 393 | where tile is navigated below current beamTo. 394 | Could make update when moved below */ 395 | Array.iteri( 396 | (i, (beamFrom, _beamTo)) => 397 | if (beamFrom > beamNone) { 398 | let beamTo = ref(0); 399 | for (j in beamFrom to tileRows - 1) { 400 | if (beamTo^ == 0) { 401 | if (state.tiles[j][i] > 0) { 402 | beamTo := j; 403 | }; 404 | }; 405 | }; 406 | if (beamTo^ == 0) { 407 | beamTo := tileRows; 408 | }; 409 | state.beams[i] = (beamFrom, beamTo^); 410 | }, 411 | state.beams 412 | ); 413 | }; 414 | 415 | let setup = tiles : stateT => { 416 | Document.addEventListener(Document.window, "keydown", e => 417 | setLastKeyCode(Document.window, getKeyEventKey(e)) 418 | ); 419 | Random.self_init(); 420 | let elQueue = ElQueue.make(); 421 | /*Mandelbrot.createCanvas();*/ 422 | /*let sdf = SdfTiles.createCanvas(); 423 | SdfTiles.draw(sdf);*/ 424 | let state = { 425 | gameState: StartScreen, 426 | action: StartScreenAction(NoAction), 427 | curEl: ElQueue.setBoardInitPos(ElQueue.pop(elQueue)), 428 | holdingEl: None, 429 | elChanged: true, 430 | elMoved: true, 431 | hasDroppedDown: false, 432 | lastTick: 0., 433 | curTime: 0., 434 | tiles: Array.make_matrix(tileRows, tileCols, 0), 435 | completedRows: 0, 436 | sceneTiles: tiles, 437 | updateTiles: true, 438 | beams: Array.make(tileCols, (beamNone, 0)), 439 | dropBeams: Array.make(tileCols, (beamNone, 0)), 440 | dropColor: Color.white(), 441 | paused: false, 442 | touchDown: None, 443 | elQueue, 444 | deltaTime: 0.0 445 | }; 446 | state; 447 | }; 448 | 449 | let newGame = state => { 450 | for (y in 0 to tileRows - 1) { 451 | for (x in 0 to tileCols - 1) { 452 | state.tiles[y][x] = 0; 453 | state.sceneTiles[tileCols * y + x] = 0; 454 | }; 455 | }; 456 | ElQueue.initQueue(state.elQueue); 457 | let state = { 458 | ...state, 459 | action: GameAction(NoAction), 460 | curEl: ElQueue.setBoardInitPos(ElQueue.pop(state.elQueue)), 461 | holdingEl: None, 462 | elChanged: true, 463 | elMoved: true, 464 | updateTiles: true, 465 | lastTick: 0., 466 | curTime: 0., 467 | gameState: Running 468 | }; 469 | updateBeams(state); 470 | state; 471 | }; 472 | 473 | let isCollision = state => 474 | List.exists( 475 | ((tileX, tileY)) => 476 | state.curEl.posY 477 | + tileY >= tileRows 478 | || state.curEl.posX 479 | + tileX < 0 480 | || state.curEl.posX 481 | + tileX > tileCols 482 | - 1 483 | || state.tiles[state.curEl.posY + tileY][state.curEl.posX + tileX] > 0, 484 | elTiles(state.curEl.el, state.curEl.rotation) 485 | ); 486 | 487 | let attemptMove = (state, (x, y)) => { 488 | let moved = { 489 | ...state, 490 | elMoved: true, 491 | curEl: { 492 | ...state.curEl, 493 | posX: state.curEl.posX + x, 494 | posY: state.curEl.posY + y 495 | } 496 | }; 497 | isCollision(moved) ? state : moved; 498 | }; 499 | 500 | let attemptMoveTest = (state, (x, y)) => { 501 | let moved = { 502 | ...state, 503 | elMoved: true, 504 | curEl: { 505 | ...state.curEl, 506 | posX: state.curEl.posX + x, 507 | posY: state.curEl.posY + y 508 | } 509 | }; 510 | isCollision(moved) ? (false, state) : (true, moved); 511 | }; 512 | 513 | /* Wall kicks http://tetris.wikia.com/wiki/SRS */ 514 | let wallTests = (state, newRotation, positions) => { 515 | let rec loop = positions => 516 | switch positions { 517 | | [] => (false, state) 518 | | [(x, y), ...rest] => 519 | let rotated = { 520 | ...state, 521 | elMoved: true, 522 | curEl: { 523 | ...state.curEl, 524 | rotation: newRotation, 525 | posX: state.curEl.posX + x, 526 | posY: state.curEl.posY - y 527 | } 528 | }; 529 | if (isCollision(rotated)) { 530 | loop(rest); 531 | } else { 532 | (true, rotated); 533 | }; 534 | }; 535 | loop(positions); 536 | }; 537 | 538 | let attemptRotateCW = state => { 539 | let newRotation = (state.curEl.rotation + 1) mod 4; 540 | /* First test for successful default rotation */ 541 | let rotated = { 542 | ...state, 543 | elMoved: true, 544 | curEl: { 545 | ...state.curEl, 546 | rotation: newRotation 547 | } 548 | }; 549 | if (! isCollision(rotated)) { 550 | (true, rotated); 551 | } else { 552 | /* Loop wall kick tests */ 553 | let testPositions = 554 | switch state.curEl.el { 555 | | Line => 556 | switch newRotation { 557 | | 1 => [((-2), 0), (1, 0), ((-2), (-1)), (1, 2)] 558 | | 2 => [((-1), 0), (2, 0), ((-1), 2), (2, (-1))] 559 | | 3 => [(2, 0), ((-1), 0), (2, 1), ((-1), (-2))] 560 | | 0 => [(1, 0), ((-2), 0), (1, (-2)), ((-2), 1)] 561 | | _ => [] 562 | } 563 | | _ => 564 | switch newRotation { 565 | | 1 => [((-1), 0), ((-1), 1), (0, (-2)), ((-1), (-2))] 566 | | 2 => [(1, 0), (1, (-1)), (0, 2), (1, 2)] 567 | | 3 => [(1, 0), (1, 1), (0, (-2)), (1, (-2))] 568 | | 0 => [((-1), 0), ((-1), (-1)), (0, 2), ((-1), 2)] 569 | | _ => [] 570 | } 571 | }; 572 | wallTests(state, newRotation, testPositions); 573 | }; 574 | }; 575 | 576 | let attemptRotateCCW = state => { 577 | let newRotation = state.curEl.rotation == 0 ? 3 : state.curEl.rotation - 1; 578 | /* First test for successful default rotation */ 579 | let rotated = { 580 | ...state, 581 | elMoved: true, 582 | curEl: { 583 | ...state.curEl, 584 | rotation: newRotation 585 | } 586 | }; 587 | if (! isCollision(rotated)) { 588 | (true, rotated); 589 | } else { 590 | /* Loop wall kick tests */ 591 | let testPositions = 592 | switch state.curEl.el { 593 | | Line => 594 | switch newRotation { 595 | | 1 => [(1, 0), ((-2), 0), (1, (-2)), ((-2), 1)] 596 | | 2 => [((-2), 0), (1, 0), ((-2), (-1)), (1, 2)] 597 | | 3 => [((-1), 0), (2, 0), ((-1), 2), (2, (-1))] 598 | | 0 => [(2, 0), ((-1), 0), (2, 1), ((-1), (-2))] 599 | | _ => [] 600 | } 601 | | _ => 602 | switch newRotation { 603 | | 1 => [((-1), 0), ((-1), 1), (0, (-2)), ((-1), (-2))] 604 | | 2 => [((-1), 0), ((-1), (-1)), (0, 2), ((-1), 2)] 605 | | 3 => [(1, 0), (1, 1), (0, (-2)), (1, (-2))] 606 | | 0 => [(1, 0), (1, (-1)), (0, 2), (1, 2)] 607 | | _ => [] 608 | } 609 | }; 610 | wallTests(state, newRotation, testPositions); 611 | }; 612 | }; 613 | 614 | let elToTiles = state => 615 | List.iter( 616 | ((tileX, tileY)) => { 617 | let posy = state.curEl.posY + tileY; 618 | let posx = state.curEl.posX + tileX; 619 | state.tiles[state.curEl.posY + tileY][state.curEl.posX + tileX] = 620 | state.curEl.color; 621 | state.sceneTiles[posy * tileCols + posx] = state.curEl.color - 1; 622 | }, 623 | elTiles(state.curEl.el, state.curEl.rotation) 624 | ); 625 | 626 | let processGameInput = (state, gameAction) => 627 | switch gameAction { 628 | | MoveLeft => { 629 | ...attemptMove(state, ((-1), 0)), 630 | action: GameAction(NoAction) 631 | } 632 | | MoveRight => {...attemptMove(state, (1, 0)), action: GameAction(NoAction)} 633 | | BlockLeft => 634 | let data = 635 | Hashtbl.find(centerRadius, (state.curEl.el, state.curEl.rotation)); 636 | let leftPos = state.curEl.posX + data.offsetX; 637 | let toMove = 638 | leftPos 639 | - ( 640 | if (leftPos > 9) { 641 | 9; 642 | } else if (leftPos > 3) { 643 | 3; 644 | } else { 645 | 0; 646 | } 647 | ); 648 | { 649 | ... 650 | List.fold_left( 651 | (state, _) => attemptMove(state, ((-1), 0)), 652 | state, 653 | Util.listRange(toMove) 654 | ), 655 | action: GameAction(NoAction) 656 | }; 657 | | BlockRight => 658 | let data = 659 | Hashtbl.find(centerRadius, (state.curEl.el, state.curEl.rotation)); 660 | let leftPos = state.curEl.posX + data.offsetX; 661 | let toMove = 662 | ( 663 | if (leftPos < 3) { 664 | 3; 665 | } else if (leftPos < 9) { 666 | 9; 667 | } else { 668 | 14; 669 | } 670 | ) 671 | - leftPos; 672 | { 673 | ... 674 | List.fold_left( 675 | (state, _) => attemptMove(state, (1, 0)), 676 | state, 677 | Util.listRange(toMove) 678 | ), 679 | action: GameAction(NoAction) 680 | }; 681 | | BlockEnd => 682 | let data = 683 | Hashtbl.find(centerRadius, (state.curEl.el, state.curEl.rotation)); 684 | let rightPos = state.curEl.posX + data.offsetX + data.width; 685 | let toMove = 686 | ( 687 | if (rightPos < 3) { 688 | 3; 689 | } else if (rightPos < 9) { 690 | 9; 691 | } else { 692 | 12; 693 | } 694 | ) 695 | - rightPos; 696 | { 697 | ... 698 | List.fold_left( 699 | (state, _) => attemptMove(state, (1, 0)), 700 | state, 701 | Util.listRange(toMove) 702 | ), 703 | action: GameAction(NoAction) 704 | }; 705 | | MoveBeginning => { 706 | ... 707 | List.fold_left( 708 | (state, _) => attemptMove(state, ((-1), 0)), 709 | state, 710 | Util.listRange(state.curEl.posX) 711 | ), 712 | action: GameAction(NoAction) 713 | } 714 | | MoveEnd => { 715 | ... 716 | List.fold_left( 717 | (state, _) => attemptMove(state, (1, 0)), 718 | state, 719 | Util.listRange(tileCols - state.curEl.posX) 720 | ), 721 | action: GameAction(NoAction) 722 | } 723 | | MoveDown => {...attemptMove(state, (0, 1)), action: GameAction(NoAction)} 724 | | CancelDown => state 725 | | DropDown => 726 | /* Drop down until collision */ 727 | let rec dropDown = state => 728 | switch (attemptMoveTest(state, (0, 1))) { 729 | | (false, state) => state 730 | | (true, state) => dropDown(state) 731 | }; 732 | dropDown({ 733 | ...state, 734 | hasDroppedDown: true, 735 | elMoved: true, 736 | action: GameAction(NoAction) 737 | }); 738 | | RotateCW => 739 | switch (attemptRotateCW(state)) { 740 | | (true, state) => {...state, action: GameAction(NoAction)} 741 | | (false, state) => {...state, action: GameAction(NoAction)} 742 | } 743 | | RotateCCW => 744 | switch (attemptRotateCCW(state)) { 745 | | (true, state) => {...state, action: GameAction(NoAction)} 746 | | (false, state) => {...state, action: GameAction(NoAction)} 747 | } 748 | | HoldElement => 749 | let holdEl = ElQueue.setHoldPos(state.curEl); 750 | let state = 751 | switch state.holdingEl { 752 | | None => nextEl({...state, holdingEl: Some(holdEl)}) 753 | | Some(nextEl) => { 754 | ...state, 755 | holdingEl: Some(holdEl), 756 | elChanged: true, 757 | elMoved: true, 758 | lastTick: state.curTime, 759 | curEl: ElQueue.setBoardInitPos(nextEl) 760 | } 761 | }; 762 | {...state, action: GameAction(NoAction)}; 763 | | Pause => { 764 | ...state, 765 | gameState: Paused, 766 | action: PauseAction(NoAction), 767 | paused: true 768 | } 769 | | Help => { 770 | ...state, 771 | gameState: HelpScreen, 772 | action: HelpScreenAction(NoAction) 773 | } 774 | | NoAction => state 775 | }; 776 | 777 | /* Touchdown process (animation) completed */ 778 | let afterTouchdown = state => { 779 | let curTime = state.curTime +. state.deltaTime; 780 | let state = 781 | switch state.touchDown { 782 | | None => state 783 | | Some(touchDown) => 784 | if (touchDown.completedRows) { 785 | /* Move rows above completed down */ 786 | Array.iter( 787 | currentRow => 788 | for (y in currentRow downto 1) { 789 | state.tiles[y] = Array.copy(state.tiles[y - 1]); 790 | for (tileIdx in y * tileCols to y * tileCols + tileCols - 1) { 791 | state.sceneTiles[tileIdx] = state.sceneTiles[tileIdx - tileCols]; 792 | }; 793 | }, 794 | touchDown.rows 795 | ); 796 | {...state, touchDown: None, updateTiles: true}; 797 | } else { 798 | {...state, touchDown: None}; 799 | } 800 | }; 801 | let state = nextEl(state); 802 | updateBeams(state); 803 | if (isCollision(state)) { 804 | {...state, action: GameOverAction(NoAction), gameState: GameOver}; 805 | } else { 806 | {...state, curTime, lastTick: curTime}; 807 | }; 808 | }; 809 | 810 | let elementHasTouchedDown = (state, isDropDown) => { 811 | /* Put element into tiles */ 812 | elToTiles(state); 813 | /* Check for completed rows */ 814 | let completedRows = 815 | Array.map( 816 | tileRow => 817 | ! 818 | Array.fold_left( 819 | (hasEmpty, tileState) => hasEmpty || tileState == 0, 820 | false, 821 | tileRow 822 | ), 823 | state.tiles 824 | ); 825 | /* Get array with indexes of completed rows */ 826 | let (_, completedRowIndexes) = 827 | Array.fold_left( 828 | ((i, rows), completed) => 829 | completed ? (i + 1, Array.append(rows, [|i|])) : (i + 1, rows), 830 | (0, [||]), 831 | completedRows 832 | ); 833 | let numCompleted = Array.length(completedRowIndexes); 834 | let completedRows = numCompleted > 0; 835 | if (! completedRows && ! isDropDown) { 836 | /* No dropdown and no completed rows, run afterTouchdown directly */ 837 | afterTouchdown({ 838 | ...state, 839 | updateTiles: true, 840 | completedRows: state.completedRows + numCompleted 841 | }); 842 | } else { 843 | { 844 | /* Initiate process of touchdown involving animations */ 845 | ...state, 846 | updateTiles: true, 847 | completedRows: state.completedRows + numCompleted, 848 | touchDown: 849 | Some({ 850 | rows: completedRowIndexes, 851 | completedRows, 852 | state: TouchDown.TouchDownInit, 853 | isDropDown, 854 | elapsed: 0.0, 855 | drawn: false 856 | }) 857 | }; 858 | }; 859 | }; 860 | 861 | let regularGameLogic = (state, isNewTick, curTime) => { 862 | let (state, isTouchdown) = 863 | if (isNewTick) { 864 | switch state.action { 865 | | GameAction(CancelDown) => (state, false) 866 | | _ => 867 | switch (attemptMoveTest(state, (0, 1))) { 868 | | (true, state) => (state, false) 869 | | (false, state) => (state, true) 870 | } 871 | }; 872 | } else { 873 | (state, false); 874 | }; 875 | if (state.elMoved) { 876 | updateBeams(state); 877 | }; 878 | switch isTouchdown { 879 | | false => { 880 | ...state, 881 | curTime, 882 | lastTick: isNewTick ? curTime : state.lastTick 883 | } 884 | | true => elementHasTouchedDown(state, false) 885 | }; 886 | }; 887 | 888 | /* Called after input action is processed */ 889 | let gameLogic = state => { 890 | let timeStep = state.deltaTime; 891 | let curTime = state.curTime +. timeStep; 892 | if (! state.hasDroppedDown) { 893 | let isNewTick = curTime > state.lastTick +. Config.tickDuration; 894 | regularGameLogic(state, isNewTick, curTime); 895 | } else { 896 | /* Element has dropped down */ 897 | let dropColor = colors[state.curEl.color]; 898 | /* Set dropbeams, 899 | copy drop beams "from", "last" from position after drop */ 900 | Array.iteri( 901 | (i, (from, _last)) => state.dropBeams[i] = (from, beamNone), 902 | state.beams 903 | ); 904 | let state = {...state, dropColor}; 905 | /* Set new "last" from current position */ 906 | List.iter( 907 | ((x, y)) => { 908 | let pointX = state.curEl.posX + x; 909 | let pointY = state.curEl.posY + y; 910 | let (beamFrom, beamTo) = state.dropBeams[pointX]; 911 | if (beamTo < pointY) { 912 | state.dropBeams[pointX] = (beamFrom, pointY); 913 | }; 914 | }, 915 | elTiles(state.curEl.el, state.curEl.rotation) 916 | ); 917 | /* Update regular beams */ 918 | updateBeams(state); 919 | elementHasTouchedDown(state, true); 920 | }; 921 | }; 922 | 923 | let rec processGameAction = (state, action: gameAction) => { 924 | let mainProcess = state => { 925 | let state = processGameInput(state, action); 926 | switch state.gameState { 927 | | Running => gameLogic(state) 928 | | _ => processAction(state) 929 | }; 930 | }; 931 | /* Check for touchdown in progress */ 932 | switch state.touchDown { 933 | | None => mainProcess(state) 934 | | Some(touchDown) => 935 | switch touchDown.state { 936 | | Done => mainProcess(afterTouchdown(state)) 937 | | _ => state 938 | } 939 | }; 940 | } 941 | and processStartScreenAction = (state, action: startScreenAction) => 942 | switch action { 943 | | NoAction => state 944 | | Help => 945 | processAction({ 946 | ...state, 947 | gameState: StartHelp, 948 | action: HelpScreenAction(NoAction) 949 | }) 950 | | StartGame => 951 | processAction({...state, gameState: Running, action: GameAction(NoAction)}) 952 | } 953 | and processHelpScreenAction = (state, action: helpScreenAction) => 954 | switch action { 955 | | NoAction => state 956 | | Resume => 957 | processAction({...state, gameState: Running, action: GameAction(NoAction)}) 958 | } 959 | and processPauseAction = (state, action: pauseAction) => 960 | switch action { 961 | | NoAction => state 962 | | Help => 963 | processAction({ 964 | ...state, 965 | gameState: HelpScreen, 966 | action: HelpScreenAction(NoAction) 967 | }) 968 | | Resume => 969 | processAction({ 970 | ...state, 971 | gameState: Running, 972 | paused: false, 973 | action: GameAction(NoAction) 974 | }) 975 | } 976 | and processNextLevelAction = (state, action: nextLevelAction) => 977 | switch action { 978 | | NoAction => state 979 | | NextLevel => 980 | processAction({...state, gameState: Running, action: GameAction(NoAction)}) 981 | } 982 | and processGameOverAction = (state, action: gameOverAction) => 983 | switch action { 984 | | NoAction => state 985 | | Help => 986 | processAction({ 987 | ...state, 988 | gameState: HelpScreen, 989 | action: HelpScreenAction(NoAction) 990 | }) 991 | | NewGame => newGame(state) 992 | } 993 | and processAction = state => 994 | /* Possibly merge what is now 995 | gamestate and action? */ 996 | switch (state.gameState, state.action) { 997 | | (Running, GameAction(action)) => processGameAction(state, action) 998 | | (StartScreen, StartScreenAction(action)) => 999 | processStartScreenAction(state, action) 1000 | | (HelpScreen, HelpScreenAction(action)) => 1001 | processHelpScreenAction(state, action) 1002 | | (StartHelp, HelpScreenAction(action)) => 1003 | processHelpScreenAction(state, action) 1004 | | (Paused, PauseAction(action)) => processPauseAction(state, action) 1005 | | (NextLevel, NextLevelAction(action)) => 1006 | processNextLevelAction(state, action) 1007 | | (GameOver, GameOverAction(action)) => processGameOverAction(state, action) 1008 | | _ => failwith("Invalid gamestate and action") 1009 | }; 1010 | 1011 | let keyPressed = (state, canvas: Gpu.Canvas.t) => { 1012 | let keyCode = canvas.keyboard.keyCode; 1013 | switch state.gameState { 1014 | | StartScreen => 1015 | StartScreenAction( 1016 | switch keyCode { 1017 | | N => StartGame 1018 | | _ => 1019 | switch (lastKeyCode(Document.window)) { 1020 | | "?" => Help 1021 | | _ => NoAction 1022 | } 1023 | } 1024 | ) 1025 | | Running => 1026 | /* Allow different commands on touchDown */ 1027 | switch state.touchDown { 1028 | | None => 1029 | GameAction( 1030 | switch keyCode { 1031 | | H => MoveLeft 1032 | | L => MoveRight 1033 | | W => BlockRight 1034 | | E => BlockEnd 1035 | | B => BlockLeft 1036 | | J => MoveDown 1037 | | K => CancelDown 1038 | | S 1039 | | R => RotateCCW 1040 | | C => RotateCW 1041 | | D 1042 | | X => HoldElement 1043 | | Period => DropDown 1044 | | Space => Pause 1045 | | _ => 1046 | switch (lastKeyCode(Document.window)) { 1047 | | "0" 1048 | | "_" => MoveBeginning 1049 | | "$" => MoveEnd 1050 | | "?" => Help 1051 | | _ => NoAction 1052 | } 1053 | } 1054 | ) 1055 | | Some(_) => 1056 | GameAction( 1057 | switch keyCode { 1058 | | Space => Pause 1059 | | _ => 1060 | switch (lastKeyCode(Document.window)) { 1061 | | "?" => Help 1062 | | _ => NoAction 1063 | } 1064 | } 1065 | ) 1066 | } 1067 | | Paused => 1068 | PauseAction( 1069 | switch keyCode { 1070 | | Space => Resume 1071 | | _ => 1072 | switch (lastKeyCode(Document.window)) { 1073 | | "?" => Help 1074 | | _ => NoAction 1075 | } 1076 | } 1077 | ) 1078 | | HelpScreen => 1079 | HelpScreenAction( 1080 | switch keyCode { 1081 | | Space => Resume 1082 | | _ => NoAction 1083 | } 1084 | ) 1085 | | StartHelp => 1086 | HelpScreenAction( 1087 | switch keyCode { 1088 | | N => Resume 1089 | | _ => NoAction 1090 | } 1091 | ) 1092 | | NextLevel => 1093 | NextLevelAction( 1094 | switch keyCode { 1095 | | N => NextLevel 1096 | | _ => NoAction 1097 | } 1098 | ) 1099 | | GameOver => 1100 | GameOverAction( 1101 | switch keyCode { 1102 | | N => NewGame 1103 | | _ => 1104 | switch (lastKeyCode(Document.window)) { 1105 | | "?" => Help 1106 | | _ => NoAction 1107 | } 1108 | } 1109 | ) 1110 | }; 1111 | }; -------------------------------------------------------------------------------- /src/gridLight.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | uniform mat3 texTrans; 6 | varying vec3 screenP; 7 | 8 | void main() { 9 | vec2 transformed = (vec3(position, 1.0) * layout).xy; 10 | screenP = vec3(transformed, 0.0); 11 | // Todo: Better support texTrans etc to avoid 12 | // manual bickering 13 | gl_Position = vec4((vec3(position, 1.0) * texTrans).xy, 0.0, 1.0); 14 | } 15 | |}; 16 | 17 | let fragmentSource = (pointLight, camera) => 18 | {| 19 | precision mediump float; 20 | varying vec3 screenP; 21 | 22 | |} 23 | ++ Light.PointLight.getLightFunction(pointLight, camera) 24 | ++ {| 25 | 26 | void main() { 27 | // Global pointLight (not using local position) 28 | vec3 pointLight = lighting(vec3(0.0), screenP, vec3(0.0, 0.0, 1.0)); 29 | gl_FragColor = vec4(pointLight, 1.0); 30 | } 31 | |}; 32 | 33 | open Gpu; 34 | 35 | let makeNode = () => { 36 | let pointLight = 37 | Light.PointLight.make(~pos=StaticPos(Config.pointLight), ~specular=24, ()); 38 | let camera = Camera.make(Config.camera); 39 | Scene.( 40 | makeNode( 41 | ~key="gridLight", 42 | ~vertShader=Shader.make(vertexSource), 43 | ~fragShader=Shader.make(fragmentSource(pointLight, camera)), 44 | ~drawTo=TextureRGB, 45 | ~texTransUniform=true, 46 | () 47 | ) 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /src/gridProgram.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | uniform mat3 tileShadowsMat; 6 | uniform mat3 beamsMat; 7 | uniform mat3 dropMat; 8 | uniform mat3 gridLightMat; 9 | varying vec2 vPosition; 10 | varying vec2 tileShadowsPos; 11 | varying vec2 beamsPos; 12 | varying vec2 dropPos; 13 | varying vec2 gridLightPos; 14 | 15 | uniform vec2 pixelSize; 16 | // Coords to take off lines on sides 17 | varying vec2 lineCoords; 18 | varying vec2 singlePixel; 19 | 20 | varying vec2 coord; 21 | void main() { 22 | vPosition = position; 23 | coord = (position + 1.0) * 0.5; 24 | singlePixel = 2.0/pixelSize; 25 | lineCoords = vec2( 26 | position.x + singlePixel.x, 27 | position.y 28 | ); 29 | vec2 transformed = (vec3(position, 1.0) * layout).xy; 30 | tileShadowsPos = (vec3(position, 1.0) * tileShadowsMat).xy; 31 | beamsPos = (vec3(position, 1.0) * beamsMat).xy; 32 | dropPos = (vec3(position, 1.0) * dropMat).xy; 33 | gridLightPos = (vec3(position, 1.0) * gridLightMat).xy; 34 | gl_Position = vec4(transformed.xy, 0.0, 1.0); 35 | } 36 | |}; 37 | 38 | let fragmentSource = {| 39 | precision mediump float; 40 | uniform vec3 bg; 41 | uniform vec3 lineColor; 42 | uniform vec2 elPos; 43 | uniform vec3 elColor; 44 | uniform vec3 dropColor; 45 | uniform vec2 pixelSize; 46 | uniform sampler2D tiles; 47 | uniform sampler2D tileShadows; 48 | uniform sampler2D beams; 49 | uniform sampler2D drop; 50 | uniform sampler2D gridLight; 51 | uniform vec4 centerRadius; 52 | 53 | varying vec2 vPosition; 54 | varying vec2 lineCoords; 55 | varying vec2 singlePixel; 56 | varying vec2 tileShadowsPos; 57 | varying vec2 beamsPos; 58 | varying vec2 dropPos; 59 | varying vec2 gridLightPos; 60 | 61 | // Normalized to 0.0 - 1.0 62 | varying vec2 coord; 63 | 64 | const float numCols = 12.0; 65 | const float numRows = 26.0; 66 | const vec2 aspect = vec2(numCols / numRows, 1.0); 67 | const float colSize = 2.0 / numCols; 68 | const float rowSize = 2.0 / numRows; 69 | const float wordSize = 1.0; 70 | const float wordStart = 0.5; 71 | const float tileWidth = 1.0 / numCols; 72 | const float tileHeight = 1.0 / numRows; 73 | 74 | float random(vec2 st) { 75 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 76 | } 77 | 78 | void main() { 79 | // todo: Calculate in vertex shader? 80 | vec2 elVec = vPosition - centerRadius.xy; 81 | vec2 elVecNorm = normalize(elVec); 82 | float elVecLength = length(elVec); 83 | 84 | float lengthCoef = max(1.5 - elVecLength, 0.0); 85 | 86 | // Roughly light a triangle below element 87 | float triangleDir = dot(elVecNorm, vec2(0.0, -1.0)); 88 | //float noiseFactor = random(vPosition) * 0.3 + 0.7; 89 | float triangleLight = smoothstep(0.0, 0.2, triangleDir) * 0.02; 90 | vec3 lPos = vec3(centerRadius.x, centerRadius.y + 0.15, 0.25); 91 | vec3 surfaceToLight = lPos - vec3(vPosition.xy, 0.0); 92 | vec3 lVec = normalize(surfaceToLight); 93 | 94 | vec3 coneDir = normalize(vec3(0.0, -0.8, 0.2)); 95 | float lightToSurfaceAngle = degrees(acos(dot(-lVec, coneDir))); 96 | 97 | float aoi = dot(vec3(0.0, 0.0, 1.0), lVec); 98 | float attenuation = 1.0 / (1.0 + 100.0 * pow(length(surfaceToLight), 2.0)); 99 | float endDegree = 60.0 + centerRadius.z * 30.0; 100 | float startDegree = endDegree - 25.0 - centerRadius.z * 20.0; 101 | attenuation = attenuation * (1.0 - smoothstep(startDegree, endDegree, lightToSurfaceAngle)); 102 | triangleLight = aoi * attenuation; 103 | 104 | // Global pointLight (not using local position) 105 | vec3 gridLight = (texture2D(gridLight, gridLightPos).xyz * 0.7) + 0.3; 106 | 107 | // Shadow 108 | float shadow = texture2D(tileShadows, tileShadowsPos).x; 109 | vec3 color = mix(bg * gridLight, vec3(0.0, 0.0, 0.0), shadow * 0.15); 110 | 111 | // Aura light 112 | /* 113 | float skew = (vPosition.y - centerRadius.y + 0.15) / 0.8; 114 | skew = 1.0; 115 | float auraLight = max(0.15 - length(elVec * centerRadius.wz) * skew, 0.0) * 0.05; 116 | color = color + elColor * auraLight; 117 | */ 118 | 119 | // Line 120 | float xblank = (mod(lineCoords.x, colSize) > singlePixel.x) ? 1.0 : 0.0; 121 | float x3blank = (mod(lineCoords.x - wordStart, wordSize) > singlePixel.x * 2.0) ? 1.0 : 0.0; 122 | float yblank = (mod(lineCoords.y, rowSize) > singlePixel.y) ? 1.0 : 0.0; 123 | float lineCoef = (1.0 - xblank * yblank * x3blank) * 0.9 + (1.0 - x3blank) * 0.1; 124 | color = mix(color, lineColor * gridLight, lineCoef); 125 | 126 | // Beam 127 | vec3 beam = texture2D(beams, beamsPos).xyz; 128 | color = mix(color, elColor, (beam.x == 0.0) ? 0.0 : 0.05); 129 | 130 | // Dropbeam 131 | float dropBeam = texture2D(drop, dropPos).x; 132 | color = mix(color, dropColor, dropBeam * 0.2); 133 | 134 | // Color + triangleLight 135 | vec3 tLight = triangleLight * bg * elColor; 136 | tLight = pow(tLight, vec3(1.0/2.2)); 137 | color = color + tLight; 138 | // Gamma correction 139 | gl_FragColor = vec4(color, 1.0); 140 | } 141 | |}; 142 | 143 | open Gpu; 144 | 145 | let makeNode = 146 | ( 147 | boardColor, 148 | lineColor, 149 | tilesTex, 150 | tileShadows, 151 | beamNode, 152 | dropNode, 153 | dropColor, 154 | sdfTiles, 155 | elState: SceneState.elState, 156 | centerRadius, 157 | completedRows 158 | ) => { 159 | let gridLight = GridLight.makeNode(); 160 | Scene. 161 | /*let bg = UVec3f.vals(0.08, 0.12, 0.22);*/ 162 | ( 163 | makeNode( 164 | ~key="grid", 165 | /* 166 | ~margin=Scene.MarginRBLT( 167 | Scale(0.01), 168 | Scale(0.002), 169 | Scale(0.01), 170 | Scale(0.005) 171 | ), 172 | */ 173 | ~vertShader=Shader.make(vertexSource), 174 | ~fragShader=Shader.make(fragmentSource), 175 | ~uniforms=[ 176 | ("bg", boardColor), 177 | ("lineColor", lineColor), 178 | ("elPos", elState.pos), 179 | ("elColor", elState.color), 180 | ("dropColor", dropColor), 181 | ("centerRadius", centerRadius), 182 | ("completedRows", completedRows) 183 | ], 184 | ~pixelSizeUniform=true, 185 | ~textures=[ 186 | ("tiles", tilesTex), 187 | ("tileShadows", SceneTex.node(tileShadows)), 188 | ("beams", SceneTex.node(beamNode)), 189 | ("drop", SceneTex.node(dropNode)), 190 | ("gridLight", SceneTex.node(gridLight)) 191 | ], 192 | ~deps=[sdfTiles, beamNode, tileShadows, dropNode, gridLight], 193 | () 194 | ) 195 | ); 196 | }; 197 | -------------------------------------------------------------------------------- /src/index.re: -------------------------------------------------------------------------------- 1 | open Gpu; 2 | 3 | open Config; 4 | 5 | open SceneState; 6 | 7 | let resize = _state => (); 8 | 9 | let makeElState = () => { 10 | let pos = Scene.UMat3f.id(); 11 | let color = Scene.UVec3f.zeros(); 12 | let vo = Scene.SVertexObject.makeQuad(~usage=DynamicDraw, ()); 13 | {vo, pos, elPos: Data.Vec2.zeros(), color}; 14 | }; 15 | 16 | let makeSceneLight = camera => { 17 | open Light; 18 | let dirLight = 19 | Directional.make(~dir=StaticDir(Data.Vec3.make(0.4, 0.3, 0.3)), ()); 20 | let pointLight = 21 | PointLight.make(~pos=StaticPos(Config.pointLight), ~specular=12, ()); 22 | ProgramLight.make(dirLight, [pointLight], camera); 23 | }; 24 | 25 | let setBg = state => { 26 | open Color; 27 | let bg = Hsl.fromRgb(fromFloats(0.14, 0.09, 0.20)); 28 | let board = Hsl.clone(bg); 29 | Hsl.incrH(board, 20.0); 30 | let line = Hsl.clone(board); 31 | Hsl.setL(line, 0.2); 32 | Scene.UVec3f.setQuiet(state.bgColor, toVec3(Hsl.toRgb(bg))); 33 | Scene.UVec3f.setQuiet(state.boardColor, toVec3(Hsl.toRgb(board))); 34 | Scene.UVec3f.setQuiet(state.lineColor, toVec3(Hsl.toRgb(line))); 35 | state; 36 | }; 37 | 38 | let setup = _canvas => { 39 | /* Element position and color uniforms */ 40 | let beamVO = Scene.SVertexObject.makeQuad(~usage=DynamicDraw, ()); 41 | let blinkVO = Scene.SVertexObject.makeQuad(~usage=DynamicDraw, ()); 42 | let dropBeamVO = 43 | Scene.SVertexObject.make( 44 | VertexBuffer.make( 45 | [||], 46 | [("position", GlType.Vec2f), ("fromDrop", GlType.Float)], 47 | DynamicDraw 48 | ), 49 | Some(IndexBuffer.make([||], DynamicDraw)) 50 | ); 51 | let tiles = Array.make(tileRows * tileCols, 0); 52 | /* Texture with tiles data */ 53 | let tilesTex = 54 | Scene.SceneTex.tex( 55 | Texture.make( 56 | IntDataTexture(Some(tiles), tileCols, tileRows), 57 | Texture.Luminance, 58 | Texture.NearestFilter 59 | ) 60 | ); 61 | let elState = makeElState(); 62 | let dropColor = Scene.UVec3f.zeros(); 63 | let gameState = Game.setup(tiles); 64 | let camera = Camera.make(Config.camera); 65 | let sceneLight = makeSceneLight(camera); 66 | let elCenterRadius = Scene.UVec4f.zeros(); 67 | let elLightPos = Scene.UVec3f.zeros(); 68 | let elLight = 69 | Light.PointLight.make( 70 | ~pos=Light.DynamicPos(elLightPos), 71 | ~color=Light.DynamicColor(elState.color), 72 | ~specular=0, 73 | ~factor=0.4, 74 | () 75 | ); 76 | let sceneAndElLight = { 77 | ...sceneLight, 78 | points: [elLight, ...sceneLight.points] 79 | }; 80 | let bgColor = Scene.UVec3f.zeros(); 81 | let boardColor = Scene.UVec3f.zeros(); 82 | let lineColor = Scene.UVec3f.zeros(); 83 | let state = { 84 | tiles, 85 | sceneLayout: StartScreen, 86 | tilesTex, 87 | elState, 88 | elCenterRadius, 89 | nextEls: [|makeElState(), makeElState(), makeElState()|], 90 | holdingEl: makeElState(), 91 | beamVO, 92 | dropBeamVO, 93 | dropColor, 94 | blinkVO, 95 | sceneLight, 96 | elLightPos, 97 | sceneAndElLight, 98 | bgColor, 99 | boardColor, 100 | lineColor, 101 | gameState, 102 | fontLayout: FontText.FontLayout.make(FontStore.make(~initSize=1, ())), 103 | completedRows: Scene.UFloat.zero() 104 | }; 105 | setBg(state); 106 | }; 107 | 108 | let createBoardNode = state => { 109 | /* Todo: Greyscale textures, and implicit setup via pool by some param */ 110 | /* Sdf tiles give 3d texture to tiles */ 111 | let sdfTiles = 112 | SdfTiles.makeNode( 113 | float_of_int(tileCols), 114 | float_of_int(tileRows), 115 | state.sceneLight, 116 | ~key="sdfTiles", 117 | ~drawTo=Scene.TextureRGB, 118 | () 119 | ); 120 | /* Beam from current element downwards */ 121 | let beamNode = TileBeam.makeNode(state.beamVO); 122 | /* Drop down animation */ 123 | let dropNode = DropBeams.makeNode(state.dropBeamVO); 124 | /* Shadow of tiles */ 125 | let tileShadows = TileShadows.makeNode(state.tilesTex); 126 | /* Tiles draw */ 127 | let tilesDraw = TilesDraw.makeNode(state.tilesTex, sdfTiles); 128 | /* Cur el node */ 129 | let currEl = CurrEl.makeNode(state.elState, sdfTiles); 130 | /* Grid node draws background to board */ 131 | let gridNode = 132 | GridProgram.makeNode( 133 | state.boardColor, 134 | state.lineColor, 135 | state.tilesTex, 136 | tileShadows, 137 | beamNode, 138 | dropNode, 139 | state.dropColor, 140 | sdfTiles, 141 | state.elState, 142 | state.elCenterRadius, 143 | state.completedRows 144 | ); 145 | let tileBlink = TileBlink.makeNode(state.blinkVO); 146 | Layout.stacked( 147 | ~key="boardLayout", 148 | ~size= 149 | Scene.Aspect( 150 | float_of_int(Config.tileCols) /. float_of_int(Config.tileRows) 151 | ), 152 | [gridNode, tilesDraw, currEl, tileBlink] 153 | ); 154 | }; 155 | 156 | let createLeftRow = state => { 157 | open Geom3d; 158 | let bgShape = 159 | AreaBetweenQuads.make( 160 | Quad.make( 161 | Point.make(-1.0, -0.45, 0.0), 162 | Point.make(1.0, -0.7, 0.0), 163 | Point.make(1.0, 1.0, 0.0), 164 | Point.make(-1.0, 1.0, 0.0) 165 | ), 166 | Quad.make( 167 | Point.make(-0.7, -0.287, 0.5), 168 | Point.make(0.8, -0.34, 0.5), 169 | Point.make(0.8, 1.0, 0.5), 170 | Point.make(-0.7, 1.0, 0.5) 171 | ) 172 | ); 173 | Layout.stacked( 174 | ~size=Scene.Dimensions(Scale(0.22), Scale(1.0)), 175 | ~vAlign=Scene.AlignTop, 176 | [ 177 | Node3d.make( 178 | AreaBetweenQuads.makeVertexObject(bgShape, ()), 179 | ~size=Scene.(Dimensions(Scale(1.0), Scale(0.6))), 180 | ~light=state.sceneAndElLight, 181 | () 182 | ), 183 | CacheResult.makeNode( 184 | ~transparent=true, 185 | ~partialDraw=true, 186 | ~size=Scene.(Dimensions(Scale(1.0), Scale(0.6))), 187 | Layout.vertical( 188 | ~margin= 189 | Scene.( 190 | MarginRBLT(Scale(0.0), Scale(0.0), Scale(0.0), Scale(0.025)) 191 | ), 192 | ~spacing=Scene.Scale(0.09), 193 | ~vAlign=Scene.AlignTop, 194 | [ 195 | FontDraw.makeSimpleNode( 196 | "HOLD", 197 | "digitalt", 198 | state.fontLayout, 199 | ~opacity=0.7, 200 | ~height=0.27, 201 | ~align=FontText.Center, 202 | () 203 | ), 204 | DrawElement.makeNode(state.holdingEl, state.sceneLight) 205 | ] 206 | ), 207 | () 208 | ) 209 | ] 210 | ); 211 | }; 212 | 213 | let createRightRow = state => { 214 | open Geom3d; 215 | let bgShape = 216 | AreaBetweenQuads.make( 217 | Quad.make( 218 | Point.make(-1.0, -0.95, 0.0), 219 | Point.make(1.0, -0.7, 0.0), 220 | Point.make(1.0, 1.0, 0.0), 221 | Point.make(-1.0, 1.0, 0.0) 222 | ), 223 | Quad.make( 224 | Point.make(-0.7, -0.54, 0.5), 225 | Point.make(0.8, -0.5, 0.5), 226 | Point.make(0.8, 1.0, 0.5), 227 | Point.make(-0.7, 1.0, 0.5) 228 | ) 229 | ); 230 | Layout.stacked( 231 | ~size=Scene.Dimensions(Scale(0.22), Scale(1.0)), 232 | ~vAlign=Scene.AlignTop, 233 | [ 234 | Node3d.make( 235 | AreaBetweenQuads.makeVertexObject(bgShape, ()), 236 | ~size=Scene.(Dimensions(Scale(1.0), Scale(0.6))), 237 | ~light=state.sceneAndElLight, 238 | () 239 | ), 240 | CacheResult.makeNode( 241 | ~transparent=true, 242 | ~partialDraw=true, 243 | ~size=Scene.(Dimensions(Scale(1.0), Scale(0.6))), 244 | Layout.vertical( 245 | ~key="nextElements", 246 | ~margin= 247 | Scene.( 248 | MarginRBLT(Scale(0.0), Scale(0.0), Scale(0.0), Scale(0.025)) 249 | ), 250 | ~spacing=Scene.Scale(0.09), 251 | ~vAlign=Scene.AlignTop, 252 | [ 253 | FontDraw.makeSimpleNode( 254 | "NEXT", 255 | "digitalt", 256 | state.fontLayout, 257 | ~key="next", 258 | ~opacity=0.7, 259 | ~height=0.27, 260 | ~align=FontText.Center, 261 | () 262 | ), 263 | DrawElement.makeNode(state.nextEls[0], state.sceneLight), 264 | DrawElement.makeNode(state.nextEls[1], state.sceneLight), 265 | DrawElement.makeNode(state.nextEls[2], state.sceneLight) 266 | ] 267 | ), 268 | () 269 | ) 270 | ] 271 | ); 272 | }; 273 | 274 | let createStartScreen = state => { 275 | let vimtrisText = 276 | FontText.( 277 | block( 278 | ~font="digitalt", 279 | ~height=0.25, 280 | ~align=FontText.Center, 281 | ~color=Color.fromFloats(1.0, 1.0, 1.0), 282 | [ 283 | styledText(~color=Game.colors[2], "V"), 284 | styledText(~color=Game.colors[3], "i"), 285 | styledText(~color=Game.colors[4], "m"), 286 | styledText(~color=Game.colors[5], "t"), 287 | styledText(~color=Game.colors[6], "r"), 288 | styledText(~color=Game.colors[7], "i"), 289 | styledText(~color=Game.colors[8], "s"), 290 | block([ 291 | styled(~height=0.08, [text("Press N to play\n")]), 292 | styled(~height=0.06, [text("Press ? for help")]) 293 | ]) 294 | ] 295 | ) 296 | ); 297 | Layout.vertical( 298 | ~key="startScreen", 299 | ~vAlign=Scene.AlignMiddle, 300 | [FontDraw.makePartNode(vimtrisText, state.fontLayout, ~height=0.5, ())] 301 | ); 302 | }; 303 | 304 | let createPauseScreen = state => { 305 | let pauseText = 306 | FontText.( 307 | block( 308 | ~align=Center, 309 | [ 310 | styledText( 311 | ~height=0.235, 312 | "\n\n" 313 | ), /* Some space, should use/have padding/margin */ 314 | styledText(~height=0.26, "Pause") 315 | ] 316 | ) 317 | ); 318 | let helpText = 319 | FontText.( 320 | block( 321 | ~align=Center, 322 | [ 323 | styledText( 324 | ~height=0.058, 325 | "\nPress Space to continue\nPress ? for help" 326 | ) 327 | ] 328 | ) 329 | ); 330 | Layout.vertical( 331 | ~key="pauseScreen", 332 | ~hidden=true, 333 | [ 334 | FontDraw.makePartNode( 335 | pauseText, 336 | state.fontLayout, 337 | ~key="pauseText", 338 | ~height=0.42, 339 | ~opacity=0.6, 340 | () 341 | ), 342 | FontDraw.makePartNode( 343 | helpText, 344 | state.fontLayout, 345 | ~key="pauseHelpText", 346 | ~height=0.25, 347 | () 348 | ) 349 | ] 350 | ); 351 | }; 352 | 353 | let createGameOverScreen = state => { 354 | let gameOverText = 355 | FontText.( 356 | block( 357 | ~align=Center, 358 | [styledText(~height=0.23, ~lineHeight=1.4, "Game\nover")] 359 | ) 360 | ); 361 | let helpText = 362 | FontText.( 363 | block( 364 | ~align=Center, 365 | [ 366 | styledText( 367 | ~height=0.045, 368 | ~lineHeight=2.4, 369 | "\nPress N to start a new game\nPress ? for help" 370 | ) 371 | ] 372 | ) 373 | ); 374 | Layout.vertical( 375 | ~key="gameOverScreen", 376 | ~hidden=true, 377 | [ 378 | FontDraw.makePartNode( 379 | gameOverText, 380 | state.fontLayout, 381 | ~key="gameOverText", 382 | ~height=0.27, 383 | () 384 | ), 385 | FontDraw.makePartNode( 386 | helpText, 387 | state.fontLayout, 388 | ~key="gameOverHelpText", 389 | ~height=0.15, 390 | () 391 | ) 392 | ] 393 | ); 394 | }; 395 | 396 | let createHelpScreen = state => { 397 | let leftStyle = FontText.blockStyle(~align=FontText.Right, ~scaleX=0.45, ()); 398 | let rightStyle = 399 | FontText.blockStyle( 400 | ~align=Left, 401 | ~color=Color.fromFloats(0.75, 0.7, 0.8), 402 | ~scaleX=0.55, 403 | () 404 | ); 405 | let helpBlocks = 406 | List.fold_left( 407 | (blocks, (key, command)) => 408 | FontText.[ 409 | block(~style=rightStyle, [text("- " ++ command)]), 410 | block(~style=leftStyle, [text(key)]), 411 | ...blocks 412 | ], 413 | [], 414 | [ 415 | ("Space", "pause"), 416 | ("H", "move left"), 417 | ("L", "move right"), 418 | ("J", "move down"), 419 | ("K", "cancel down"), 420 | ("C", "rotate clockwise"), 421 | ("S", "rotate counter clockwise"), 422 | (".", "drop"), 423 | ("W", "move block right"), 424 | ("B", "move block left"), 425 | ("0", "move leftmost"), 426 | ("$", "move rightmost") 427 | ] 428 | ); 429 | /* Hacked in newline for space, margin/padding would be nice */ 430 | let helpText = 431 | FontText.( 432 | block( 433 | ~font="digitalt", 434 | ~height=0.12, 435 | [block(~height=0.042, [text("\n"), ...List.rev(helpBlocks)])] 436 | ) 437 | ); 438 | Layout.vertical( 439 | ~key="helpScreen", 440 | ~hidden=true, 441 | [ 442 | Layout.stacked( 443 | ~size=Aspect(1.0 /. 0.21), 444 | [ 445 | FontDraw.makePartNode( 446 | FontText.( 447 | block( 448 | ~align=FontText.Center, 449 | [ 450 | styled( 451 | ~height=0.12, 452 | [ 453 | text("Help\n"), 454 | styled( 455 | ~height=0.06, 456 | ~color=Color.fromFloats(0.8, 0.85, 0.9), 457 | [text("Press N to play")] 458 | ) 459 | ] 460 | ) 461 | ] 462 | ) 463 | ), 464 | state.fontLayout, 465 | ~key="startHelp", 466 | ~height=0.21, 467 | () 468 | ), 469 | FontDraw.makePartNode( 470 | FontText.( 471 | block( 472 | ~align=FontText.Center, 473 | [ 474 | styled( 475 | ~height=0.12, 476 | [ 477 | text("Help\n"), 478 | styled( 479 | ~height=0.05, 480 | ~color=Color.fromFloats(0.8, 0.85, 0.9), 481 | [text("Press Space to continue")] 482 | ) 483 | ] 484 | ) 485 | ] 486 | ) 487 | ), 488 | state.fontLayout, 489 | ~hidden=true, 490 | ~key="gameHelp", 491 | ~height=0.21, 492 | () 493 | ) 494 | ] 495 | ), 496 | FontDraw.makePartNode(helpText, state.fontLayout, ~height=0.56, ()) 497 | ] 498 | ); 499 | }; 500 | 501 | let createNextLevelScreen = state => 502 | Layout.vertical([ 503 | FontDraw.makeSimpleNode( 504 | "Level complete", 505 | "digitalt", 506 | state.fontLayout, 507 | ~key="gameOver", 508 | ~height=0.25, 509 | ~align=FontText.Center, 510 | () 511 | ), 512 | FontDraw.makeSimpleNode( 513 | "Press N to start new level", 514 | "digitalt", 515 | state.fontLayout, 516 | ~height=0.08, 517 | ~align=FontText.Center, 518 | () 519 | ) 520 | ]); 521 | 522 | let createRootNode = state => { 523 | open Scene; 524 | let mainSize = Aspect((14.0 +. 10.0) /. 26.0); 525 | let maxHeight = Pixels(680.0); 526 | Layout.stacked( 527 | ~key="root", 528 | ~size=Dimensions(Scale(1.0), Scale(1.0)), 529 | ~vAlign=AlignMiddle, 530 | [ 531 | Background.makeNode( 532 | state.bgColor, 533 | [ 534 | Layout.horizontal( 535 | ~key="gameHorizontal", 536 | ~size=mainSize, 537 | ~maxHeight, 538 | ~hidden=true, 539 | ~hAlign=AlignCenter, 540 | [ 541 | createLeftRow(state), 542 | createBoardNode(state), 543 | createRightRow(state) 544 | ] 545 | ) 546 | ] 547 | ), 548 | Mask.makeNode(), 549 | Layout.stacked( 550 | ~maxHeight, 551 | [ 552 | createStartScreen(state), 553 | createPauseScreen(state), 554 | createHelpScreen(state), 555 | createGameOverScreen(state) 556 | ] 557 | ) 558 | ] 559 | ); 560 | }; 561 | 562 | let createScene = (canvas, state) => { 563 | let scene = 564 | Scene.make(canvas, state, createRootNode(state), ~drawListDebug=false, ()); 565 | /* Queue deps from root */ 566 | Scene.queueDeps(scene, scene.root.id); 567 | let anim = 568 | Animate.anim( 569 | AnimNodeUniform(Scene.getNodeUnsafe(scene, "background"), "anim"), 570 | AnimFloat(0.0, 1.0), 571 | ~easing=Scene.SineOut, 572 | ~duration=1.5, 573 | () 574 | ); 575 | Scene.doAnim(scene, anim); 576 | scene; 577 | }; 578 | 579 | let resetElState = (scene, elState) => 580 | Scene.SVertexObject.updateQuads(scene, elState.vo, [||]); 581 | 582 | let updateElTiles = 583 | (scene, el: Game.elData, elState, rows, cols, uCenterRadius, isSideEl) => { 584 | /* todo: Consider caching some of this */ 585 | let tiles = Game.elTiles(el.el, el.rotation); 586 | let color = Color.toArray(Game.colors[el.color]); 587 | let x = el.posX; 588 | let y = el.posY; 589 | Scene.UVec3f.set(scene, elState.color, color); 590 | /* Translate to -1.0 to 1.0 coords */ 591 | let tileHeight = 2.0 /. float_of_int(rows); 592 | let tileWidth = 2.0 /. float_of_int(cols); 593 | /* Translation */ 594 | let xPos = (-1.) +. float_of_int(x) *. tileWidth; 595 | let yPos = 1. +. float_of_int(y) *. (-. tileHeight); 596 | Data.Vec2.set(elState.elPos, xPos, yPos); 597 | let elPos = 598 | if (isSideEl) { 599 | Data.Mat3.scaleTrans(0.65, 0.65, xPos *. 0.325, yPos *. 0.325); 600 | } else { 601 | Data.Mat3.trans(xPos, yPos); 602 | }; 603 | Scene.UMat3f.set(scene, elState.pos, elPos); 604 | /* If we got center radius uniform (gridProgram currently uses this) */ 605 | switch uCenterRadius { 606 | | None => () 607 | | Some(uCenterRadius) => 608 | let data = Hashtbl.find(Game.centerRadius, (el.el, el.rotation)); 609 | let cx = xPos +. data.centerX *. tileWidth; 610 | let cy = yPos +. data.centerY *. tileHeight; 611 | let rx = data.radiusX *. tileWidth; 612 | let ry = data.radiusY *. tileHeight; 613 | Scene.UVec4f.set(scene, uCenterRadius, Data.Vec4.make(cx, cy, rx, ry)); 614 | }; 615 | let currElTiles = 616 | Array.concat( 617 | List.map( 618 | ((tileX, tileY)) => { 619 | /* 2x coord system with y 1.0 at top and -1.0 at bottom */ 620 | let tileYScaled = float_of_int(tileY * (-1)) *. tileHeight; 621 | let tileXScaled = float_of_int(tileX) *. tileWidth; 622 | /* Bottom left, Bottom right, Top right, Top left */ 623 | [| 624 | tileXScaled, 625 | tileYScaled -. tileHeight, 626 | tileXScaled +. tileWidth, 627 | tileYScaled -. tileHeight, 628 | tileXScaled +. tileWidth, 629 | tileYScaled, 630 | tileXScaled, 631 | tileYScaled 632 | |]; 633 | }, 634 | tiles 635 | ) 636 | ); 637 | Scene.SVertexObject.updateQuads(scene, elState.vo, currElTiles); 638 | }; 639 | 640 | let updateBeams = (scene, beams, beamsVO, withFromDrop) => { 641 | /* Create vertices for beams */ 642 | let rowHeight = 2.0 /. float_of_int(tileRows); 643 | let colWidth = 2.0 /. float_of_int(tileCols); 644 | let (_, vertices) = 645 | Array.fold_left( 646 | ((i, vertices), (beamFrom, beamTo)) => 647 | if (beamFrom > Game.beamNone) { 648 | let lx = (-1.0) +. float_of_int(i) *. colWidth; 649 | let rx = (-1.0) +. (float_of_int(i) *. colWidth +. colWidth); 650 | let by = 1.0 -. (float_of_int(beamTo) *. rowHeight +. rowHeight); 651 | let ty = 1.0 -. float_of_int(beamFrom) *. rowHeight; 652 | let beamVertices = 653 | if (withFromDrop) { 654 | [|lx, by, 1.0, rx, by, 1.0, rx, ty, 0.0, lx, ty, 0.0|]; 655 | } else { 656 | [|lx, by, rx, by, rx, ty, lx, ty|]; 657 | }; 658 | (i + 1, Array.append(vertices, beamVertices)); 659 | } else { 660 | (i + 1, vertices); 661 | }, 662 | (0, [||]), 663 | beams 664 | ); 665 | Scene.SVertexObject.updateQuads(scene, beamsVO, vertices); 666 | }; 667 | 668 | let blinkInit = (scene, state, blinkRows) => { 669 | /* Update VO */ 670 | let rowHeight = 2.0 /. float_of_int(Config.tileRows); 671 | let vertices = 672 | Array.fold_left( 673 | (vertices, rowIdx) => 674 | Array.append( 675 | vertices, 676 | [| 677 | (-1.0), 678 | 1.0 -. rowHeight *. float_of_int(rowIdx + 1), 679 | 1.0, 680 | 1.0 -. rowHeight *. float_of_int(rowIdx + 1), 681 | 1.0, 682 | 1.0 -. rowHeight *. float_of_int(rowIdx), 683 | (-1.0), 684 | 1.0 -. rowHeight *. float_of_int(rowIdx) 685 | |] 686 | ), 687 | [||], 688 | blinkRows 689 | ); 690 | Scene.SVertexObject.updateQuads(scene, state.blinkVO, vertices); 691 | /* Show tileBlink node */ 692 | switch (Scene.getNode(scene, "tileBlink")) { 693 | | Some(tileBlink) => Scene.showNode(scene, tileBlink) 694 | | _ => () 695 | }; 696 | }; 697 | 698 | let blinkEnd = scene => 699 | /* Hide tileBlink node */ 700 | switch (Scene.getNode(scene, "tileBlink")) { 701 | | Some(tileBlink) => Scene.hideNode(scene, tileBlink) 702 | | _ => () 703 | }; 704 | 705 | let drawGame = (state, scene) => { 706 | /* Handle element moved (changed position or rotated) */ 707 | if (state.gameState.elMoved) { 708 | updateElTiles( 709 | scene, 710 | state.gameState.curEl, 711 | state.elState, 712 | tileRows, 713 | tileCols, 714 | Some(state.elCenterRadius), 715 | false 716 | ); 717 | updateBeams(scene, state.gameState.beams, state.beamVO, false); 718 | /* We want elState.pos transformed by layout as light pos for element */ 719 | let elPos = state.elState.elPos; 720 | let gridNode = Scene.getNodeUnsafe(scene, "grid"); 721 | switch gridNode.layoutUniform { 722 | | Some(Gpu.UniformMat3f(layout)) => 723 | open Data; 724 | let pointPos = Mat3.vecmul(layout^, Vec3.fromVec2(elPos, 1.0)); 725 | Vec3.setZ(pointPos, 0.25); 726 | let lightAnim = "elLightAnim"; 727 | Scene.clearAnim(scene, lightAnim); 728 | if (state.gameState.elChanged) { 729 | /* Set pos without animation */ 730 | Scene.UVec3f.set( 731 | scene, 732 | state.elLightPos, 733 | pointPos 734 | ); 735 | } else { 736 | let from = Scene.UVec3f.get(state.elLightPos); 737 | let (isDropDown, completedRows) = 738 | switch state.gameState.touchDown { 739 | | None => (false, false) 740 | | Some(touchDown) => (touchDown.isDropDown, touchDown.completedRows) 741 | }; 742 | let anim = 743 | if (isDropDown) { 744 | /* Let anim go from reduced distance */ 745 | Vec3.setY( 746 | from, 747 | Vec3.getY(pointPos) 748 | -. (Vec3.getY(pointPos) -. Vec3.getY(from)) 749 | /. 1.5 750 | ); 751 | if (completedRows) { 752 | /* SineIn easing for extra boom effect */ 753 | Animate.anim( 754 | Animate.AnimUniform(state.elLightPos), 755 | Animate.AnimVec3(from, pointPos), 756 | ~key=lightAnim, 757 | ~duration=Config.dropDownBeforeTouchDown, 758 | ~easing=Scene.SineIn, 759 | ~frameInterval=2, 760 | () 761 | ); 762 | } else { 763 | Animate.anim( 764 | Animate.AnimUniform(state.elLightPos), 765 | Animate.AnimVec3(from, pointPos), 766 | ~key=lightAnim, 767 | ~duration=Config.dropDownBeforeTick /. 2.0, 768 | ~frameInterval=2, 769 | () 770 | ); 771 | }; 772 | } else { 773 | Animate.anim( 774 | Animate.AnimUniform(state.elLightPos), 775 | Animate.AnimVec3(from, pointPos), 776 | ~key=lightAnim, 777 | ~duration=Config.tickDuration /. 3.0, 778 | ~frameInterval=3, 779 | () 780 | ); 781 | }; 782 | Scene.doAnim(scene, anim); 783 | }; 784 | | _ => () 785 | }; 786 | }; 787 | /* Handle update tiles */ 788 | if (state.gameState.updateTiles) { 789 | switch state.tilesTex.texture.inited { 790 | | Some(inited) => 791 | inited.update = true; 792 | /* Data is updated on the array in place */ 793 | Scene.queueUpdates(scene, state.tilesTex.nodes); 794 | | None => () 795 | }; 796 | }; 797 | switch state.gameState.touchDown { 798 | | None => 799 | /* Handle new element */ 800 | if (state.gameState.elChanged) { 801 | /* Redraw next elements */ 802 | let _ = 803 | Queue.fold( 804 | (i, nextEl) => { 805 | updateElTiles(scene, nextEl, state.nextEls[i], 3, 4, None, true); 806 | i + 1; 807 | }, 808 | 0, 809 | state.gameState.elQueue 810 | ); 811 | /* Handle holding element */ 812 | switch state.gameState.holdingEl { 813 | | Some(holdEl) => 814 | updateElTiles(scene, holdEl, state.holdingEl, 3, 4, None, true) 815 | | None => resetElState(scene, state.holdingEl) 816 | }; 817 | } 818 | | Some(touchDown) => 819 | switch touchDown.state { 820 | | Game.TouchDown.Blinking => 821 | let elapsed = touchDown.elapsed +. scene.canvas.deltaTime; 822 | let untilElapsed = 823 | touchDown.isDropDown ? 824 | Config.dropDownBeforeTouchDown +. Config.blinkTime : Config.blinkTime; 825 | if (elapsed >= untilElapsed) { 826 | /* End blinking */ 827 | blinkEnd(scene); 828 | /* Update completed rows uniform */ 829 | Scene.UFloat.set( 830 | scene, 831 | state.completedRows, 832 | float_of_int(state.gameState.completedRows) 833 | ); 834 | /* Signal done to game logic by setting Done state */ 835 | state.gameState = { 836 | ...state.gameState, 837 | touchDown: Some({...touchDown, state: Game.TouchDown.Done}) 838 | }; 839 | } else { 840 | /* Update elapsed time */ 841 | state.gameState = { 842 | ...state.gameState, 843 | touchDown: Some({...touchDown, elapsed}) 844 | }; 845 | }; 846 | | Game.TouchDown.DropOnly => 847 | let elapsed = touchDown.elapsed +. scene.canvas.deltaTime; 848 | if (touchDown.completedRows) { 849 | if (elapsed >= Config.dropDownBeforeTouchDown) { 850 | /* Start blink */ 851 | blinkInit(scene, state, touchDown.rows); 852 | state.gameState = { 853 | ...state.gameState, 854 | touchDown: 855 | Some({...touchDown, state: Game.TouchDown.Blinking, elapsed}) 856 | }; 857 | } else { 858 | /* Update elapsed time */ 859 | state.gameState = { 860 | ...state.gameState, 861 | touchDown: Some({...touchDown, elapsed}) 862 | }; 863 | }; 864 | } else if (elapsed >= Config.dropDownBeforeTick) { 865 | /* Update completed rows uniform */ 866 | Scene.UFloat.set( 867 | scene, 868 | state.completedRows, 869 | float_of_int(state.gameState.completedRows) 870 | ); 871 | /* Signal done to start next tick */ 872 | state.gameState = { 873 | ...state.gameState, 874 | touchDown: Some({...touchDown, state: Game.TouchDown.Done}) 875 | }; 876 | } else { 877 | /* Update elapsed time */ 878 | state.gameState = { 879 | ...state.gameState, 880 | touchDown: Some({...touchDown, elapsed}) 881 | }; 882 | }; 883 | | Game.TouchDown.TouchDownInit => 884 | if (touchDown.isDropDown) { 885 | let dropNode = Scene.getNodeUnsafe(scene, "dropBeams"); 886 | /* Update dropbeams */ 887 | updateBeams(scene, state.gameState.dropBeams, state.dropBeamVO, true); 888 | Scene.UVec3f.set( 889 | scene, 890 | state.dropColor, 891 | Color.toVec3(state.gameState.dropColor) 892 | ); 893 | let dropAnim = 894 | Animate.anim( 895 | AnimNodeUniform(dropNode, "sinceDrop"), 896 | AnimFloat(0.0, 1.0), 897 | ~easing=Scene.SineOut, 898 | ~duration=Config.dropAnimDuration, 899 | () 900 | ); 901 | Scene.doAnim(scene, dropAnim); 902 | /* When this is dropdown, don't blink immediately */ 903 | state.gameState = { 904 | ...state.gameState, 905 | touchDown: Some({...touchDown, state: Game.TouchDown.DropOnly}) 906 | }; 907 | } else { 908 | /* Touchdown init without dropdown, so there should be completed rows 909 | and blink right away */ 910 | blinkInit(scene, state, touchDown.rows); 911 | state.gameState = { 912 | ...state.gameState, 913 | touchDown: Some({...touchDown, state: Game.TouchDown.Blinking}) 914 | }; 915 | } 916 | | Game.TouchDown.Done => () 917 | } 918 | }; 919 | /* Reset state used by scene */ 920 | state.gameState = { 921 | ...state.gameState, 922 | hasDroppedDown: false, 923 | elChanged: false, 924 | elMoved: false, 925 | updateTiles: false 926 | }; 927 | state; 928 | }; 929 | 930 | let setScreenState = (state, screen) => {...state, sceneLayout: screen}; 931 | 932 | let showMask = (scene, last) => { 933 | let maskNode = Scene.getNodeUnsafe(scene, "mask"); 934 | let animKey = "maskAnim"; 935 | Scene.clearAnim(scene, animKey); 936 | let anim = 937 | Animate.anim( 938 | AnimNodeUniform(maskNode, "anim"), 939 | AnimFloat(0.0, last), 940 | ~key=animKey, 941 | ~easing=Scene.SineOut, 942 | ~duration=3.0, 943 | () 944 | ); 945 | Scene.showNode(scene, maskNode); 946 | Scene.doAnim(scene, anim); 947 | }; 948 | 949 | let hideMask = scene => Scene.hideNodeByKey(scene, "mask"); 950 | 951 | let draw = (state, scene, canvas: Gpu.Canvas.t) => { 952 | state.gameState = 953 | Game.processAction({...state.gameState, deltaTime: canvas.deltaTime}); 954 | /* Maybe polymorphic variants here, does it make sense/possible? 955 | and some better state management/page navigation? */ 956 | let state = 957 | switch state.gameState.gameState { 958 | | Running => 959 | let state = 960 | switch state.sceneLayout { 961 | | GameScreen => state 962 | | StartScreen => 963 | Scene.hideNodeByKey(scene, "startScreen"); 964 | Scene.showNodeByKey(scene, "gameHorizontal"); 965 | setScreenState(state, GameScreen); 966 | | PauseScreen => 967 | Scene.hideNodeByKey(scene, "pauseScreen"); 968 | hideMask(scene); 969 | setScreenState(state, GameScreen); 970 | | HelpScreen => 971 | Scene.hideNodeByKey(scene, "helpScreen"); 972 | Scene.showNodeByKey(scene, "gameHorizontal"); 973 | hideMask(scene); 974 | setScreenState(state, GameScreen); 975 | | GameOverScreen => 976 | Scene.hideNodeByKey(scene, "gameOverScreen"); 977 | hideMask(scene); 978 | setScreenState(state, GameScreen); 979 | | StartHelp => 980 | Scene.hideNodeByKey(scene, "helpScreen"); 981 | Scene.showNodeByKey(scene, "gameHorizontal"); 982 | setScreenState(state, GameScreen); 983 | }; 984 | drawGame(state, scene); 985 | | StartScreen => state 986 | | StartHelp => 987 | switch state.sceneLayout { 988 | | StartHelp => state 989 | | StartScreen => 990 | Scene.hideNodeByKey(scene, "startScreen"); 991 | Scene.showNodeByKey(scene, "helpScreen"); 992 | setScreenState(state, StartHelp); 993 | | _ => failwith("Starthelp should only be triggered from start screen") 994 | } 995 | | HelpScreen => 996 | switch state.sceneLayout { 997 | | HelpScreen => state 998 | | GameScreen => 999 | Scene.hideNodeByKey(scene, "gameHorizontal"); 1000 | Scene.showNodeByKey(scene, "helpScreen"); 1001 | Scene.hideNodeByKey(scene, "startHelp"); 1002 | Scene.showNodeByKey(scene, "gameHelp"); 1003 | setScreenState(state, HelpScreen); 1004 | | PauseScreen => 1005 | Scene.hideNodeByKey(scene, "gameHorizontal"); 1006 | Scene.hideNodeByKey(scene, "pauseScreen"); 1007 | Scene.hideNodeByKey(scene, "startHelp"); 1008 | Scene.showNodeByKey(scene, "gameHelp"); 1009 | hideMask(scene); 1010 | Scene.showNodeByKey(scene, "helpScreen"); 1011 | setScreenState(state, HelpScreen); 1012 | | GameOverScreen => 1013 | Scene.hideNodeByKey(scene, "gameHorizontal"); 1014 | Scene.hideNodeByKey(scene, "gameOverScreen"); 1015 | Scene.hideNodeByKey(scene, "startHelp"); 1016 | Scene.showNodeByKey(scene, "gameHelp"); 1017 | Scene.showNodeByKey(scene, "helpScreen"); 1018 | setScreenState(state, HelpScreen); 1019 | | StartScreen => 1020 | Scene.hideNodeByKey(scene, "startScreen"); 1021 | Scene.showNodeByKey(scene, "helpScreen"); 1022 | setScreenState(state, HelpScreen); 1023 | | _ => failwith("Helpscreen from state not recognized") 1024 | } 1025 | | Paused => 1026 | switch state.sceneLayout { 1027 | | PauseScreen => state 1028 | | GameScreen => 1029 | showMask(scene, 0.6); 1030 | Scene.showNodeByKey(scene, "pauseScreen"); 1031 | setScreenState(state, PauseScreen); 1032 | | _ => failwith("Paused from other than game screen") 1033 | } 1034 | | NextLevel => state /* Not implemented */ 1035 | | GameOver => 1036 | switch state.sceneLayout { 1037 | | GameOverScreen => state 1038 | | GameScreen => 1039 | showMask(scene, 1.0); 1040 | Scene.showNodeByKey(scene, "gameOverScreen"); 1041 | setScreenState(state, GameOverScreen); 1042 | | _ => failwith("Game over from other than game screen") 1043 | } 1044 | }; 1045 | Scene.update(scene); 1046 | state; 1047 | }; 1048 | 1049 | let keyPressed = (state, canvas) => { 1050 | ...state, 1051 | gameState: { 1052 | ...state.gameState, 1053 | action: Game.keyPressed(state.gameState, canvas) 1054 | } 1055 | }; 1056 | 1057 | let (viewportX, viewportY) = Gpu.Canvas.getViewportSize(); 1058 | 1059 | Scene.run( 1060 | viewportX, 1061 | viewportY, 1062 | setup, 1063 | createScene, 1064 | draw, 1065 | ~keyPressed, 1066 | ~resize, 1067 | () 1068 | ); -------------------------------------------------------------------------------- /src/mandelbrot.re: -------------------------------------------------------------------------------- 1 | let maxIterations = 99999; 2 | 3 | type complexNumber = { 4 | real: float, 5 | imaginary: float 6 | }; 7 | 8 | let multComplex = (c1, c2) => { 9 | real: c1.real *. c2.real -. c1.imaginary *. c2.imaginary, 10 | imaginary: c1.real *. c2.imaginary +. c1.imaginary *. c2.real 11 | }; 12 | 13 | let addComplex = (c1, c2) => { 14 | real: c1.real +. c2.real, 15 | imaginary: c1.imaginary +. c2.imaginary 16 | }; 17 | 18 | let absComplex = c => 19 | Js_math.sqrt(c.real *. c.real +. c.imaginary *. c.imaginary); 20 | 21 | let calcIterations = (x, y) => { 22 | let z = {real: 0., imaginary: 0.}; 23 | let c = {real: x, imaginary: y}; 24 | let rec iterate = (z, c, iterations) => { 25 | let z = addComplex(multComplex(z, z), c); 26 | if (absComplex(z) > 2.0) { 27 | iterations; 28 | } else { 29 | iterate(z, c, iterations + 1); 30 | }; 31 | }; 32 | iterate(z, c, 0); 33 | }; 34 | 35 | module Constants = RGLConstants; 36 | 37 | module Gl = Reasongl.Gl; 38 | 39 | let vertexSource = {| 40 | precision mediump float; 41 | attribute vec2 position; 42 | varying vec2 vPosition; 43 | void main() { 44 | vPosition = position; 45 | gl_Position = vec4(position, 0.0, 1.0); 46 | } 47 | |}; 48 | 49 | let fragmentSource = {| 50 | precision mediump float; 51 | varying vec2 vPosition; 52 | const int max_iterations = 80; 53 | 54 | float range_over(float range, int iterations) { 55 | return float(range - min(range, float(iterations))) / range; 56 | } 57 | 58 | void main() { 59 | vec2 z = vec2(0.0, 0.0); 60 | float z_r_temp = 0.0; 61 | vec2 c = vec2(vPosition.x * 1.4, vPosition.y * 1.2) * 0.15 + 0.4; 62 | int iterations = 0; 63 | for (int i = 0; i <= max_iterations; i++) { 64 | // z = z*z + c 65 | z_r_temp = (z.x*z.x - z.y*z.y) + c.x; 66 | z = vec2(z_r_temp, ((z.x*z.y) * 2.0) + c.y); 67 | if (length(z) > 2.0) { 68 | iterations = i; 69 | break; 70 | } 71 | } 72 | if (iterations == 0) { 73 | iterations = max_iterations; 74 | } 75 | float l_extra = length(z) - 2.0; 76 | vec3 color = vec3( 77 | range_over(4.0 + l_extra, iterations), 78 | range_over(12.0 + l_extra, iterations), 79 | range_over(80.0 + l_extra, iterations) 80 | ); 81 | gl_FragColor = vec4(color, 1.0); 82 | } 83 | |}; 84 | 85 | open Gpu; 86 | 87 | let createCanvas = () => { 88 | let canvas = Canvas.init(400, 300); 89 | let drawState = 90 | DrawState.init( 91 | canvas.context, 92 | Program.make( 93 | Shader.make(vertexSource), 94 | Shader.make(fragmentSource), 95 | VertexBuffer.quadAttribs(), 96 | [] 97 | ), 98 | VertexBuffer.makeQuad(), 99 | Some(IndexBuffer.makeQuad()), 100 | [], 101 | canvas.gpuState 102 | ); 103 | DrawState.draw(drawState, canvas); 104 | }; 105 | -------------------------------------------------------------------------------- /src/mask.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | void main() { 5 | gl_Position = vec4(position, 0.0, 1.0); 6 | } 7 | |}; 8 | 9 | let fragmentSource = {| 10 | precision mediump float; 11 | uniform float anim; 12 | 13 | void main() { 14 | gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5 * anim); 15 | } 16 | |}; 17 | 18 | let makeNode = () => 19 | Scene.( 20 | Scene.makeNode( 21 | ~key="mask", 22 | ~vertShader=Gpu.Shader.make(vertexSource), 23 | ~fragShader=Gpu.Shader.make(fragmentSource), 24 | ~transparent=true, 25 | ~hidden=true, 26 | ~uniforms=[("anim", UFloat.make(0.0))], 27 | ~size=Dimensions(Scale(1.0), Scale(1.0)), 28 | () 29 | ) 30 | ); 31 | -------------------------------------------------------------------------------- /src/sceneState.re: -------------------------------------------------------------------------------- 1 | type elState = { 2 | vo: Scene.sceneVertexObject, 3 | pos: Scene.sceneUniform, 4 | elPos: Data.Vec2.t, 5 | color: Scene.sceneUniform 6 | }; 7 | 8 | /* Todo: better "page"/scene system */ 9 | type sceneLayout = 10 | | StartScreen 11 | | GameScreen 12 | | PauseScreen 13 | | HelpScreen 14 | | StartHelp 15 | | GameOverScreen; 16 | 17 | type sceneState = { 18 | tiles: array(int), 19 | sceneLayout, 20 | fontLayout: FontText.FontLayout.t, 21 | tilesTex: Scene.sceneTexture, 22 | elState, 23 | elCenterRadius: Scene.sceneUniform, 24 | nextEls: array(elState), 25 | holdingEl: elState, 26 | beamVO: Scene.sceneVertexObject, 27 | dropBeamVO: Scene.sceneVertexObject, 28 | dropColor: Scene.sceneUniform, 29 | blinkVO: Scene.sceneVertexObject, 30 | sceneLight: Light.ProgramLight.t, 31 | elLightPos: Scene.sceneUniform, 32 | sceneAndElLight: Light.ProgramLight.t, 33 | bgColor: Scene.sceneUniform, 34 | boardColor: Scene.sceneUniform, 35 | lineColor: Scene.sceneUniform, 36 | mutable gameState: Game.stateT, 37 | completedRows: Scene.sceneUniform 38 | }; 39 | -------------------------------------------------------------------------------- /src/sdfTiles.re: -------------------------------------------------------------------------------- 1 | let sdfDist = (cols, rows, tileSpace) => { 2 | let colsGl = string_of_float(cols); 3 | let rowsGl = string_of_float(rows); 4 | {| 5 | float cols = |} 6 | ++ colsGl 7 | ++ {|; 8 | float rows = |} 9 | ++ rowsGl 10 | ++ {|; 11 | float cols2 = cols * 2.0; 12 | float rows2 = rows * 2.0; 13 | point.x = mod(point.x, 1.0 / cols) - 1.0 / cols2; 14 | point.y = mod(point.y, 1.0 / rows) - 1.0 / rows2; 15 | float boxWidth = 1.0 / cols2; 16 | float boxHeight = 1.0 / rows2; 17 | float boxDepth = 0.005; 18 | float box = length(max(abs(point) - vec3(boxWidth, boxHeight, boxDepth), vec3(0.0, 0.0, 0.0))); 19 | // Octahedron towards z 20 | /* 21 | float rot = 20.0; 22 | mat3 xrot = mat3( 23 | 1.0, 0.0, 0.0, 24 | 0.0, cos(rot), -sin(rot), 25 | 0.0, sin(rot), cos(rot) 26 | ); 27 | point = point * xrot; 28 | */ 29 | float d = 0.0; 30 | // Dont know too much of what I'm doing here.. 31 | // sdpyramid from https://www.shadertoy.com/view/Xds3zN with some modifications 32 | vec3 octa = vec3(0.5 * boxHeight / boxWidth, 0.5, 0.24); 33 | d = max( d, abs( dot(point, vec3( -octa.x, 0, octa.z )) )); 34 | d = max( d, abs( dot(point, vec3( octa.x, 0, octa.z )) )); 35 | d = max( d, abs( dot(point, vec3( 0, -octa.y, octa.z )) )); 36 | d = max( d, abs( dot(point, vec3( 0, octa.y, octa.z )) )); 37 | // Some spacing added, maybe this should be calibrated to a pixel 38 | float o = d - octa.z / (rows + |} 39 | ++ string_of_float(tileSpace) 40 | ++ {|); 41 | // Intersection 42 | //return o; 43 | return max(o, box); 44 | |}; 45 | }; 46 | 47 | type sdfTiles = { 48 | cols: float, 49 | rows: float, 50 | tileSpace: float, 51 | sdfNode: SdfNode.t 52 | }; 53 | 54 | module StoreSpec = { 55 | type hash = { 56 | hCols: float, 57 | hRows: float, 58 | hTileSpace: float, 59 | hSdfNode: SdfNode.hash 60 | }; 61 | type progType = sdfTiles; 62 | let getHash = sdfTiles => { 63 | hCols: sdfTiles.cols, 64 | hRows: sdfTiles.rows, 65 | hTileSpace: sdfTiles.tileSpace, 66 | hSdfNode: SdfNode.makeHash(sdfTiles.sdfNode) 67 | }; 68 | let createProgram = sdfTiles => SdfNode.makeProgram(sdfTiles.sdfNode); 69 | let tblSize = 3; 70 | }; 71 | 72 | module Programs = ProgramStore.Make(StoreSpec); 73 | 74 | let makeNode = 75 | ( 76 | cols, 77 | rows, 78 | lighting, 79 | ~drawTo=?, 80 | ~vo=?, 81 | ~color=?, 82 | ~key=?, 83 | ~model=?, 84 | ~margin=?, 85 | ~tileSpace=1.3, 86 | () 87 | ) => { 88 | let aspect = cols /. rows; 89 | let fragCoords = 90 | switch model { 91 | | Some(_) => SdfNode.ByModel 92 | | None => SdfNode.ZeroToOne 93 | }; 94 | let sdfNode = 95 | SdfNode.make( 96 | sdfDist(cols, rows, tileSpace), 97 | fragCoords, 98 | model, 99 | lighting, 100 | ~vo?, 101 | ~color?, 102 | () 103 | ); 104 | let sdfTiles: sdfTiles = {cols, rows, tileSpace, sdfNode}; 105 | let program = Programs.getProgram(sdfTiles); 106 | SdfNode.makeNode( 107 | sdfNode, 108 | ~program, 109 | ~key?, 110 | ~cls="sdfTiles", 111 | ~aspect, 112 | ~drawTo?, 113 | ~margin?, 114 | () 115 | ); 116 | }; 117 | -------------------------------------------------------------------------------- /src/tetronimo.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | points: list((int, int)), 3 | points90: list((int, int)), 4 | points180: list((int, int)), 5 | points270: list((int, int)), 6 | colorIndex: int 7 | }; 8 | 9 | let make = (points, (translateX, translateY), colorIndex) => { 10 | let rotate90 = ((x, y)) => (y, x * (-1)); 11 | let points90 = List.map(rotate90, points); 12 | let points180 = List.map(rotate90, points90); 13 | let points270 = List.map(rotate90, points180); 14 | /* Translate and set upper left corner points */ 15 | let toTilePoints = ((x, y)) => ( 16 | /* Not sure about why y is flipped.. */ 17 | (x - translateX) / 2 - 1, 18 | (y - translateY) / (-2) - 1 19 | ); 20 | { 21 | points: List.map(toTilePoints, points), 22 | points90: List.map(toTilePoints, points90), 23 | points180: List.map(toTilePoints, points180), 24 | points270: List.map(toTilePoints, points270), 25 | colorIndex 26 | }; 27 | }; 28 | 29 | /* Origin is in x2 to simplify to ints */ 30 | let lineTiles = make([((-3), 1), ((-1), 1), (1, 1), (3, 1)], (1, 1), 2); 31 | 32 | let leftLTiles = make([((-2), 2), ((-2), 0), (0, 0), (2, 0)], (0, 0), 3); 33 | 34 | let rightLTiles = make([((-2), 0), (0, 0), (2, 0), (2, 2)], (0, 0), 4); 35 | 36 | let cubeTiles = make([((-1), 1), ((-1), (-1)), (1, 1), (1, (-1))], (1, 1), 5); 37 | 38 | let rightTurnTiles = make([((-2), 0), (0, 0), (0, 2), (2, 2)], (0, 0), 6); 39 | 40 | let triangleTiles = make([((-2), 0), (0, 0), (0, 2), (2, 0)], (0, 0), 7); 41 | 42 | let leftTurnTiles = make([((-2), 2), (0, 2), (0, 0), (2, 0)], (0, 0), 8); 43 | -------------------------------------------------------------------------------- /src/tileBeam.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | varying vec2 vPosition; 6 | void main() { 7 | vPosition = position; 8 | vec2 pos = (vec3(position, 1.0) * layout).xy; 9 | gl_Position = vec4(pos, 0.0, 1.0); 10 | } 11 | |}; 12 | 13 | let fragmentSource = {| 14 | precision mediump float; 15 | varying vec2 vPosition; 16 | 17 | void main() { 18 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); 19 | } 20 | |}; 21 | 22 | open Gpu; 23 | 24 | let makeNode = vo => 25 | Scene.makeNode( 26 | ~key="beams", 27 | ~vertShader=Shader.make(vertexSource), 28 | ~fragShader=Shader.make(fragmentSource), 29 | ~drawTo=Scene.TextureRGB, 30 | ~clearOnDraw=true, 31 | ~vo, 32 | () 33 | ); 34 | -------------------------------------------------------------------------------- /src/tileBlink.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | varying vec2 vPosition; 6 | void main() { 7 | vPosition = position; 8 | vec3 pos2 = vec3(position, 1.0) * layout; 9 | gl_Position = vec4(pos2.xy, 0.0, 1.0); 10 | } 11 | |}; 12 | 13 | let fragmentSource = {| 14 | precision mediump float; 15 | varying vec2 vPosition; 16 | 17 | void main() { 18 | gl_FragColor = vec4(1.0, 1.0, 1.0, 0.6); 19 | } 20 | |}; 21 | 22 | open Gpu; 23 | 24 | let makeNode = vo => 25 | Scene.makeNode( 26 | ~key="tileBlink", 27 | ~vertShader=Shader.make(vertexSource), 28 | ~fragShader=Shader.make(fragmentSource), 29 | ~transparent=true, 30 | ~hidden=true, 31 | ~vo, 32 | () 33 | ); 34 | -------------------------------------------------------------------------------- /src/tileShadows.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | varying vec2 vPosition; 6 | void main() { 7 | vPosition = position; 8 | vec2 pos = (vec3(position, 1.0) * layout).xy; 9 | gl_Position = vec4(pos, 0.0, 1.0); 10 | } 11 | |}; 12 | 13 | let fragmentSource = {| 14 | precision mediump float; 15 | varying vec2 vPosition; 16 | 17 | uniform sampler2D tiles; 18 | 19 | const float numCols = 12.0; 20 | const float numRows = 26.0; 21 | 22 | void main() { 23 | // Perspective coord 24 | vec2 aspect = vec2(numCols / numRows, 1.0); 25 | vec2 persp = vPosition + vec2( 26 | vPosition.x * 0.08, 27 | 0.0 28 | ); 29 | persp.x = clamp(persp.x, -1.0, 1.0); 30 | vec2 tilePos = vec2((persp.x + 1.0) * 0.5, (persp.y - 1.0) * -0.5); 31 | tilePos.y = tilePos.y - 0.03; 32 | float tile = texture2D(tiles, tilePos).x; 33 | vec4 tileColor = (tile > 0.0) ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0); 34 | gl_FragColor = tileColor; 35 | } 36 | |}; 37 | 38 | open Gpu; 39 | 40 | let makeNode = tilesTex => { 41 | /* Use two textures */ 42 | let toTex = Gpu.Texture.makeEmptyRgb(); 43 | let tempTex = Gpu.Texture.makeEmptyRgb(); 44 | /* First draw unblurred */ 45 | let unblurred = 46 | Scene.makeNode( 47 | ~key="tileShadows", 48 | ~vertShader=Shader.make(vertexSource), 49 | ~fragShader=Shader.make(fragmentSource), 50 | ~textures=[("tiles", tilesTex)], 51 | ~drawTo=Scene.TextureItem(toTex), 52 | () 53 | ); 54 | Blur2.makeNode(unblurred, toTex, tempTex, 4.0, 4.0); 55 | }; 56 | -------------------------------------------------------------------------------- /src/tilesDraw.re: -------------------------------------------------------------------------------- 1 | let vertexSource = {| 2 | precision mediump float; 3 | attribute vec2 position; 4 | uniform mat3 layout; 5 | uniform mat3 sdfTilesMat; 6 | varying vec2 sdfPos; 7 | varying vec2 vPosition; 8 | void main() { 9 | vPosition = position; 10 | vec3 transformed = vec3(position, 1.0) * layout; 11 | sdfPos = (vec3(position, 1.0) * sdfTilesMat).xy; 12 | gl_Position = vec4(transformed.xy, 0.0, 1.0); 13 | } 14 | |}; 15 | 16 | let fragmentSource = 17 | {| 18 | precision mediump float; 19 | varying vec2 vPosition; 20 | varying vec2 sdfPos; 21 | 22 | uniform sampler2D tiles; 23 | uniform sampler2D sdfTiles; 24 | 25 | const int numCols = 12; 26 | const int numRows = 26; 27 | 28 | void main() { 29 | vec2 tilePos = vec2((vPosition.x + 1.0) * 0.5, (vPosition.y - 1.0) * -0.5); 30 | float tile = texture2D(tiles, tilePos).x; 31 | int colorIdx = int(tile * 255.0); 32 | vec3 color = 33 | (colorIdx == 1) ? |} 34 | ++ Color.toGlsl(Game.colors[2]) 35 | ++ {| 36 | : (colorIdx == 2) ? |} 37 | ++ Color.toGlsl(Game.colors[3]) 38 | ++ {| 39 | : (colorIdx == 3) ? |} 40 | ++ Color.toGlsl(Game.colors[4]) 41 | ++ {| 42 | : (colorIdx == 4) ? |} 43 | ++ Color.toGlsl(Game.colors[5]) 44 | ++ {| 45 | : (colorIdx == 5) ? |} 46 | ++ Color.toGlsl(Game.colors[6]) 47 | ++ {| 48 | : (colorIdx == 6) ? |} 49 | ++ Color.toGlsl(Game.colors[7]) 50 | ++ {| 51 | : |} 52 | ++ Color.toGlsl(Game.colors[8]) 53 | ++ {|; 54 | vec3 sdfColor = texture2D(sdfTiles, sdfPos).xyz; 55 | //color = color * 0.5 + ((colorIdx == 0) ? vec3(0.0, 0.0, 0.0) : (sdfColor * 0.5)); 56 | /* 57 | float sdfCoef = abs(0.5 - sdfColor.x); 58 | float tileCoef = 1.0 - sdfCoef; 59 | gl_FragColor = (colorIdx == 0) ? vec4(0.0, 0.0, 0.0, 0.0) : vec4(color * tileCoef + sdfColor * sdfCoef, 1.0); 60 | */ 61 | gl_FragColor = (colorIdx == 0) ? vec4(0.0, 0.0, 0.0, 0.0) : vec4(mix(color, sdfColor, 0.4), 1.0); 62 | } 63 | |}; 64 | 65 | open Gpu; 66 | 67 | let makeNode = (tilesTex, sdfTiles) => 68 | Scene.( 69 | Scene.makeNode( 70 | ~key="tilesDraw", 71 | ~vertShader=Shader.make(vertexSource), 72 | ~fragShader=Shader.make(fragmentSource), 73 | ~uniforms=[], 74 | ~transparent=true, 75 | ~partialDraw=true, 76 | ~textures=[("tiles", tilesTex), ("sdfTiles", SceneTex.node(sdfTiles))], 77 | () 78 | ) 79 | ); 80 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './lib/js/src/index.js', 6 | mode: 'production', 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | filename: 'main.js' 10 | }, 11 | plugins: [ 12 | new UglifyJSPlugin() 13 | ] 14 | }; --------------------------------------------------------------------------------