├── .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 | };
--------------------------------------------------------------------------------