
Tetris clone written in Zig running on WebGL and WebAssembly - Hoppetosse
https://raulgrell.github.io/tetris/
======
jedisct1
So much cutting edge technology for something that could have been written in
BASIC running on a Z80 :)

That being said, Zig is an interesting, well-deigned language. Despite being
young, it already has tons of features. And compatibility with the C ABI is a
big plus.

~~~
flohofwoe
...I think it's interesting that WebAssembly moves the "code bloat problem"
into focus again, because WebAssembly apps cannot afford to have huge upfront
downloads or long installations. They must start in a second or so.

The Tetris example is under 60 kBytes (compressed), and that includes all data
as well (there's a sprite/font sheet embedded in the wasm blob). This is in
the same general size range as 8-bit BASIC programs.

That means that Zig doesn't need a big runtime to work, similar to C, but
unlike C# or Java (or even C++ / Rust, unless you ignore most of the
respective standard libraries and restrict yourself to a minimal "embedded-
programming" style).

Here's a similar example (shameless plug), a WASM C64 emulator in under 64
KByte download size. This is implemented in C:

[https://floooh.github.io/tiny8bit/c64.html](https://floooh.github.io/tiny8bit/c64.html)

I can (most likely) get to the same result in Zig, but with a much nicer
"better C" language (and build system!)

(...of course there's a huge web browser dangling off the WebAssembly blob,
but this is mostly runtime components that are not even needed for this type
of WASM programs (we really need smaller, more modularized browsers!).

~~~
bluejekyll
I’m not sure what you mean by your comments around C vs. C++/Rust. All of them
have no required runtime. All of them generally have libc supplied by your OS,
which is a runtime.

Even in C you must restrict yourself to the runtime your building on, i.e.
your target might not have libc. Rust and C++ are the same in the regard that
they are capable of doing this.

~~~
flohofwoe
There was a time when Rust executables came with an embedded jemalloc, but
that's been fixed in the meantime (I think). But that's not what I mean in the
C++ and Rust case, instead:

Both C++ and Rust make it quite easy to "accidentally" add bloat indirectly
through their standard libraries because they encourage high-level
abstractions which may "unfold" into a lot of binary code from very little
source code.

It can be avoided but requires to give up on many of the high-level language
features which Rust and C++ differentiate from (for instance) C.

~~~
bluejekyll
I can speak better for Rust in this regard, so I won't comment on C++, but I
also don't work with #[no_std] often so apologies if anything is inaccurate.
jemalloc has been optional in nightly ever since I started using the language
in 2015. It switched to the system allocator in the 1.28 release, Aug 2018, it
also stabilized the ability to define a custom Global Allocator[1].

> Rust make it quite easy to "accidentally" add bloat indirectly through their
> standard libraries

This isn't accurate, the #![no_std] feature disables stdlib in your
library/binary. It requires you to implement a few things, but it will not
compile if you use things in std [2]. You may be interested in reading the
embedded book[3] which discusses this area in a lot more detail.

> give up on many of the high-level language features

I also would disagree. You still have a powerful type system, match
statements, RAII, hygienic macros, proc_macros, all of the core library[4],
optionally have access to the alloc crate[5], cargo, simple unit-test
integration, and a growing set of #[no_std] libraries with easy access through
crates.io or git dependencies. There's a lot more of course. Point being,
there are still a lot of higher-level language features available even in a
#[no_std] context, and it's very easy to ensure that your binaries won't
compile if this is a requirement and someone tries to pull something in that
relies on std.

[1]: [https://blog.rust-lang.org/2018/08/02/Rust-1.28.html](https://blog.rust-
lang.org/2018/08/02/Rust-1.28.html)

[2]: [https://doc.rust-lang.org/nomicon/beneath-std.html](https://doc.rust-
lang.org/nomicon/beneath-std.html)

[3]: [https://rust-embedded.github.io/book/intro/no-std.html](https://rust-
embedded.github.io/book/intro/no-std.html)

[4]: [https://doc.rust-lang.org/core/](https://doc.rust-lang.org/core/)

[5]: [https://doc.rust-lang.org/alloc/](https://doc.rust-lang.org/alloc/)

------
ktpsns
Didn't know the Zig Programming language before, but its website
[https://ziglang.org](https://ziglang.org) is really an outstanding example
how a programming language should be presented: Many examples, comparisons
with other language, and even more examples. Code code code.

~~~
new4thaccount
IIRC this is because it gets posted here every month and there is always
someone complaining about those kinds of things and they addressed those
points.

It also gets brought up a bit with the Pharo Smalltalk website(lack of code
snippets), but Smalltalk is such a radically different language than what
we're mostly familiar with (C, Java, Python, JS) that showing a code snippet
is mostly pointless. People try to bring this up, but to no avail.

Sometimes the syntax just isn't very important. I think in Zig's case though,
that it is fair to ask.

------
new4thaccount
It looks like Zig has some decent documentation (something I treasure), but it
looks like file I/O is missing. I'm sure it assumes the user is at least
intermediate with C (I can only get by with lots of googling). I'll give Zig a
try if someone can put up an example of reading a .CSV file (something I do a
lot and wish I had faster tools to do) and if it is less of a hassle than
C/C++.

[https://ziglang.org/documentation/master/](https://ziglang.org/documentation/master/)

The install seems to also be a lot more straightforward than many of the other
low level languages that I have tried.

~~~
coldtea
They have several examples of reading a file etc, e.g.:

    
    
      var file = os.File.openRead(arg) catch |err| {
                      warn("Unable to open file: {}\n", @errorName(err));
                      return err;
                  };
                  defer file.close();
    

The rest should be basically string operations...

~~~
new4thaccount
I'm not sure how I missed this, but thank you for bringing it to my attention.
It seems straightforward enough. I should be able to figure out the string
operations.

~~~
jstimpfle
It's also very straightforward to just link to libc and use
fopen()/fread()/fwrite()/fclose()/fflush(). That's how I do it with my own
(much less mature) language.

------
kebman
The web page bobs around when pressing the buttons, when the game area is
smaller than the window. Is there any way to make that stop aside from making
the area smaller, so that it fits the window?

~~~
Ravengenocide
You could run something like this in the console:

    
    
        window.addEventListener("keydown", function(e) {
            // space and arrow keys
            if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
                e.preventDefault();
            }
        }, false);
    

That will prevent the scrolling when pressing the arrow keys. It's of course
not a perfect solution, and something more robust would be to check if a game
is currently running and so on, but it works as a quick hack.

------
anon777778
What is surprising to me is that adding the shadow of an element (ie. where
exactly it will land) made the game more difficult to me because I constantly
felt like running out of time.

~~~
sabas123
After playing tons of hours on jstris I can't live without it

------
pcr910303
Hmm... [Error] TypeError: null is not an object (evaluating 'gl.viewport')
(env.js:17) [Error] Unhandled Promise Rejection: TypeError: import
env:consoleLog must be an object promiseReactionJob

Looks like not working in Safari 12.1 in macOS High Sierra :-(

~~~
flohofwoe
The project uses WebGL2, which isn't supported on Safari. I guess it's just an
oversight, supporting WebGL1 is probably trivial.

~~~
Narishma
The title should mention this, then. WebGL and WebGL2 aren't the same thing.

~~~
flohofwoe
...well the difference only matters on Safari, and it is indeed surprising
that Apple isn't able to provide a WebGL2 implementation after such a long
time of having a (pretty much stalled) WebGL2 WIP version in their browsers.

IMHO it's understandable that the author assumed that WebGL2 is available
everywhere.

~~~
Narishma
It matters on more browsers than Safari. Firefox doesn't support WebGL2 on my
system for example. I think it's because it uses an older version of Angle.

------
alfonsodev
would you consider implementing a tetromino bag like the one described
here[1], I think is common among the most popular tetris implementations, it
affects a lot the game strategy, I think would make it more enjoyable.

[1][https://tetris.fandom.com/wiki/Random_Generator](https://tetris.fandom.com/wiki/Random_Generator)

~~~
AndyKelley
This WASM version is based on my native code implementation which implements
something like this.

I believe I independently discovered this concept of a tetromino bag. I've
been calling it "Gambler's Accurate Model of Reality" in a homage to Gambler's
Fallacy.

[https://github.com/raulgrell/tetris/commit/4305cd1ac1bcc9443...](https://github.com/raulgrell/tetris/commit/4305cd1ac1bcc9443320710da8f9c5915418ed1b)

That commit is from Feb 7, 2016, and you can see all the TODO comments for the
zig features that didn't exist yet :-)

------
VMG

       Uncaught TypeError: Cannot read property 'viewport' of null
        at env.js:17

(anonymous) @ env.js:17

    
    
       Uncaught (in promise) TypeError: WebAssembly.instantiate(): Import #0 module="env" error: module is not an object or function
    
    
       Chromium Version 74.0.3729.108 (Official Build) Arch Linux (64-bit)

~~~
gbanfalvi
Safari doesn't support webgl2 (you can enable it as an experimental feature in
the develop menu), so retrieving a webgl2 context returns null.

~~~
theon144
>Chromium Version 74.0.3729.108 (Official Build) Arch Linux (64-bit)

Safari?

------
kreco
Great work, I wanted to know a way to play with WebAssembly and Zig. This is
perfect.

Do you know how easy/difficult would it be to make a desktop version of it ?

One of my long term goal is to make it cross platform.

~~~
Hoppetosse
This is actually a port of the demo made by Andrew Kelley, which was
originally written for the desktop using GLFW.

My plan is to integrate my changes into the original project so that it can
export to desktop and web.

~~~
kreco
Wow, that's great!

I'm following you on Github then.

------
giornogiovanna
Do random rows get pushed from the bottom on purpose? It looks like a bug.

It's still fun though. :)

Edit: It's definitely on purpose.

~~~
Ravengenocide
I thought it looked like a bug, but it's a feature based on which level you
are on and how many lines you've filled(?). Here's the part that decides if it
should fill or not:
[https://github.com/raulgrell/tetris/blob/master/src/main.zig...](https://github.com/raulgrell/tetris/blob/master/src/main.zig#L450-L460)

------
tejuis
Bug report: The game mostly work, but sometimes it builds bogus rows on the
bottom. Makes it more difficult to get high points. Debian Linux with
Chrome...

~~~
coolreader18
I can't tell if you're joking or not, but that's not a bug, that's just what
happens in some versions of Tetris.

~~~
tejuis
OK. Not a bug, a feature. :)

------
hu3
55.4kb gziped main.wasm including graphics! Impressive!

Works smoothly.

------
rambojazz
Is there a LICENSE file or license header somewhere?

~~~
Hoppetosse
I forked a project, didn't even realize it didn't have a license. I'll ask the
original creator to decide.

------
codesushi42
Really great.

Do you know of any good resources for 2d game programming in WebAssembly?

~~~
1ba9115454
GGEZ looks good. [https://ggez.rs/](https://ggez.rs/)

~~~
oneoneone1
Maybe in the future. wasm is still not supported target, see
[https://github.com/ggez/ggez/issues/71](https://github.com/ggez/ggez/issues/71)

------
z1r011
Nice in Firefox, but my Chrome (Version 73.0.3683.103 (Official Build)
(64-bit)) on MacOS does not show the game "frame".

------
Aardwolf
Works on mobile except for the keyboard input :)

------
jstimpfle
How is the compilation to WASM/WebGL realized here? Asking out of curiosity,
but don't have a lot of time to research.

~~~
flohofwoe
As far as I can see:

Compilation to WebAssembly bytecode is done via the new WASM backend in LLVM
(the Zig compiler sits on top of LLVM).

The whole interfacing with WebGL and WebAudio is actually quite interesting.
It's not done through a runtime provided by the compiler SDK (as it is usually
done with emscripten), but instead the Zig-to-JS binding is all done
"manually" in the project itself.

There are 2 small "shim files" for WebGL/WebAudio and DOM access written in JS
here:

[https://github.com/raulgrell/tetris/blob/master/env.js](https://github.com/raulgrell/tetris/blob/master/env.js)

and here:

[https://github.com/raulgrell/tetris/blob/master/dom.js](https://github.com/raulgrell/tetris/blob/master/dom.js)

...and associated Zig interop files here:

[https://github.com/raulgrell/tetris/blob/master/src/webgl.zi...](https://github.com/raulgrell/tetris/blob/master/src/webgl.zig)

and here:

[https://github.com/raulgrell/tetris/blob/master/src/dom.zig](https://github.com/raulgrell/tetris/blob/master/src/dom.zig)

It's really surprisingly little (and clean) code for doing that sort of thing
IMHO.

------
NavekM
I noticed a bug where you cannot rotate an item next to a wall if you have not
previously rotated it.

------
waspoza
Not working on Firefox?

~~~
heinrich5991
Works for me.

------
downtide
How do I run this?

------
hestefisk
Nice work.

------
xlzrs
666

~~~
tempodox
Is that some kind of satanic incantation or just an HTML color code?

