11 ms·
Tetris clone written in Zig running on WebGL and WebAssembly
- jedisct1 7y agoSo 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 7y ago...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!).
- winter_blue 7y agoIt’s not much of a problem once you have code splitting. React supports code splitting, which lets you split large amounts JS into separate production JS files; load only what’s necessary at first, and load the rest as-needed: https://reactjs.org/docs/code-splitting.html https://reactjs.org/docs/code-splitting.html With a new programming language (like Zig), I would hope that they eventually try to have code splitting be automatic and transparent. For JS targets, you create multiple smaller final JS files instead of one giant JS file. For native targets, you could break out extra code into dynamically loaded .so or .dll files. The real challenge is making this whole process as painless and transparent as possible for the developer.
- flohofwoe 7y agoCompiled languages usually have dead-code-elimination (only code that's actually "reachable" is included)... I think the JS world invented the word "tree-shaking" for that. Not loading everything that's eventually needed at startup can be handled with loading WASM modules on demand (which can be handled the same way as loading DLLs in a native application). IMHO it's better to fight code bloat at the root, by picking a language without runtime, minimizing dependencies, and picking the right dependencies.
- bluejekyll 7y agoI’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 7y agoThere 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 7y agoI 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/
- dmitrygr 7y ago> .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. Original Tetris was under 32k, and competition implementations since have been under 512 bytes. 60K plus webkit plus OS is plenty of bloat still
- apta 7y ago> but unlike C# or Java https://github.com/MichalStrehovsky/zerosharp/blob/master/efi-no-runtime/README.md https://github.com/MichalStrehovsky/zerosharp/blob/master/ef...
- MaxBarraclough 7y ago> Despite being young, it already has tons of features Zig is aiming to be lean-and-mean, not feature-rich, but I take your point. I agree it's very impressive how quickly it's come this far.
- downtide 7y agoHow do I run this?
- jstimpfle 7y agoHow is the compilation to WASM/WebGL realized here? Asking out of curiosity, but don't have a lot of time to research.
- flohofwoe 7y agoAs 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.zig https://github.com/raulgrell/tetris/blob/master/src/webgl.zi... 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.
- fengb 7y agoZig has wasm as a built-in compile target — derived from LLVM 8 so Clang has the same functionality. The link to Dom/OpenGL is still a manual process but there’s some efforts generating the necessary JavaScript helpers.
- hestefisk 7y agoNice work.
- giornogiovanna 7y agoDo 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.
- cjpb 7y agoI noticed this too, but it correlates when you go up a "level" - so I assume it's a feature to add random rows each level.
- Ravengenocide 7y agoI 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#L450-L460 https://github.com/raulgrell/tetris/blob/master/src/main.zig...
- Insanity 7y agoI've seen that behaviour in other Tetris games as well. :)
- Hoppetosse 7y agoMore than one person has mentioned this. Maybe an audio cue will make it more obvious it's on purpose...
- anon777778 7y agoWhat 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 7y agoAfter playing tons of hours on jstris I can't live without it
- Numberwang 7y agoThe shadow seems to lag as well making it harder. On http://t3tr0s.com/#/solo http://t3tr0s.com/#/solo it doesn't lag and instead it really helps.
- konart 7y agoSame here. No matter how much I've been trying to convince myself that this is just a tip from the game...
- welly 7y agoI keep looking at the shadow rather than the block.
- VMG 7y agoUncaught 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 7y agoSafari doesn't support webgl2 (you can enable it as an experimental feature in the develop menu), so retrieving a webgl2 context returns null.
- theon144 7y ago>Chromium Version 74.0.3729.108 (Official Build) Arch Linux (64-bit) Safari?
- VMG 7y agoI think enabling chrome://flags/#enable-webgl-draft-extensions fixed it
- hu3 7y ago55.4kb gziped main.wasm including graphics! Impressive! Works smoothly.
- codesushi42 7y agoReally great. Do you know of any good resources for 2d game programming in WebAssembly?
- 1ba9115454 7y agoGGEZ looks good. https://ggez.rs/ https://ggez.rs/
- oneoneone1 7y agoMaybe in the future. wasm is still not supported target, see https://github.com/ggez/ggez/issues/71 https://github.com/ggez/ggez/issues/71
- kashug 7y agoIf you are not against using an engine, GoDot (open source) is great. It can target web using WebAssembly.
- kebman 7y agoThe 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 7y agoYou 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.
- Hoppetosse 7y agoThanks for the feedback. I've reworked the page so that it's less likely that the game does not fit in a screen without scrolling - not ideal, but it'll help!
- pcr910303 7y agoHmm... [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 7y agoThe project uses WebGL2, which isn't supported on Safari. I guess it's just an oversight, supporting WebGL1 is probably trivial.
- Narishma 7y agoThe title should mention this, then. WebGL and WebGL2 aren't the same thing.
- flohofwoe 7y ago...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 7y agoIt 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.
- rambojazz 7y agoIs there a LICENSE file or license header somewhere?
- sigzero 7y agoNone that I can see.
- Hoppetosse 7y agoI forked a project, didn't even realize it didn't have a license. I'll ask the original creator to decide.
- kreco 7y agoGreat 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 7y agoThis 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 7y agoWow, that's great! I'm following you on Github then.
- alfonsodev 7y agowould 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 7y agoThis 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/4305cd1ac1bcc9443320710da8f9c5915418ed1b https://github.com/raulgrell/tetris/commit/4305cd1ac1bcc9443... That commit is from Feb 7, 2016, and you can see all the TODO comments for the zig features that didn't exist yet :-)
- ktpsns 7y agoDidn'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 7y agoIIRC 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.
- xlzrs 7y ago666
- tempodox 7y agoIs that some kind of satanic incantation or just an HTML color code?
- new4thaccount 7y agoIt 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 7y agoThey 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 7y agoI'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 7y agoIt'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.
- abhijat 7y agoCould you be so kind as to point out where you found this example please? I could not find it in the documentation link in parent post.
- coldtea 7y agoI/O examples such as that already exist on the Zig webpage, e.g. here: https://ziglang.org/#A-fresh-take-on-error-handling https://ziglang.org/#A-fresh-take-on-error-handling But the one I posted is from GitHub, where it has e.g: https://github.com/ziglang/zig/blob/8139c5a516eaa217ed76acdf09496895451c5c5c/example/cat/main.zig https://github.com/ziglang/zig/blob/8139c5a516eaa217ed76acdf... also: https://github.com/ziglang/zig/blob/92d9cef07116da8639ac33b7fd83ba1b35f0750c/std/io/test.zig https://github.com/ziglang/zig/blob/92d9cef07116da8639ac33b7...
- tejuis 7y agoBug 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 7y agoI 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 7y agoOK. Not a bug, a feature. :)
- Aardwolf 7y agoWorks on mobile except for the keyboard input :)
- z1r011 7y agoNice in Firefox, but my Chrome (Version 73.0.3683.103 (Official Build) (64-bit)) on MacOS does not show the game "frame".
- waspoza 7y agoNot working on Firefox?
- heinrich5991 7y agoWorks for me.
- NavekM 7y agoI noticed a bug where you cannot rotate an item next to a wall if you have not previously rotated it.