commit aa42ae52f6d5fc32727ab4c8a9d8375f9247e7c8 parent b55c05b91316f2054b3046794799e7eaa5ffcd8d Author: Sebastiano Tronto <sebastiano@tronto.net> Date: Sun, 28 Sep 2025 09:34:46 +0200 Merge branch 'master' of tronto.net:sebastiano.tronto.net Diffstat:
36 files changed, 1947 insertions(+), 282 deletions(-)
diff --git a/build.sh b/build.sh @@ -22,7 +22,8 @@ copyfile() { md) t="$(markdowntitle "$file")" sed "s/TITLE/$t/" < top.html > "$ind" - lowdown "$file" >> "$ind" + lowdown --html-no-skiphtml --html-no-escapehtml \ + "$file" >> "$ind" cat bottom.html >> "$ind" ;; html) @@ -64,7 +65,7 @@ makeblogindexandfeed() { f="src/blog/$i/*.md" d="$(echo "$i" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}')" - t="$(head -n 1 $f | sed 's/# //')" + t="$(markdowntitle $f)" link="https://sebastiano.tronto.net/blog/$i" thisyear="$(echo "$d" | sed 's/-.*//')" diff --git a/src/blog/2022-06-04-gemini/gemini.md b/src/blog/2022-06-04-gemini/gemini.md @@ -192,4 +192,4 @@ and, more annoyingly for the few gemini users, I am going to use http(s) links even when a gemini counterpart is available. *Update: as of August 2023, my website is still available on gemini, -but new blog posts are no longer mirrore there* +but new blog posts are no longer mirrored there* diff --git a/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md b/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md @@ -93,7 +93,7 @@ projects, or probably it will be never done. And that's OK. My plan is to split the work into the following parts: -* Starting from the [stable branch](https://nissy.tronto.net/download) +* Starting from the [stable branch](https://nissy.tronto.net) of nissy, remove all the code that is needed only by the optimal solver and other unnecessary steps. Work on a GUI and other simple features useful for assisting fewest moves solvers. diff --git a/src/blog/2024-09-20-c-scripting/c-scripting.md b/src/blog/2024-09-20-c-scripting/c-scripting.md @@ -75,6 +75,11 @@ systems! The command is also included in FreeBSD (since 4.3) and MacOS (since 13), and there are probably workarounds to make the same concept work without it. +*Edit 2025-06-09: as a reader points out, `realpath` is included +in the most recent POSIX standard, +[POSIX.1-2024](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/realpath.html), +so it should be more widely available in the future.* + ## Credits I did not come up with this trick - I read about it in at least diff --git a/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md b/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md @@ -269,7 +269,7 @@ A classic example is [`std::tuple`](https://en.cppreference.com/w/cpp/utility/tuple), which works similarly to `std::pair`, but accepts any number of items. -## Contraints and concepts +## Constraints and concepts To explain the last features I want to talk about, I am going to use a simlpe, albeit slightly unusual, example: let's implement a class diff --git a/src/blog/2025-04-04-qt-minimal/qt-minimal.md b/src/blog/2025-04-04-qt-minimal/qt-minimal.md @@ -98,3 +98,38 @@ And finally you can enjoy your new app: ``` ./run ``` + +## Update 2025-04-07 - now with AI! + +When I shared this post on LinkedIn, I asked if anyone could prompt an +LLM to build a QT app without QT Creator or CMake. A colleague did, +and the catbot's (pun intended) response can be found +[here](https://chat.mistral.ai/chat/fa3e3a76-0e8c-4135-94a1-ae7735174e93). + +This solution is different from the one I found. The bot opted for using +[qmake](https://doc.qt.io/qt-6/qmake-manual.html), another tool bundled +with QT, like moc and uic. This tool reads a `.pro` file that describes +the project's structure and build configuration, and it generates a +`Makefile`. This method is a bit more black-boxy than the one I found, +but it does not require more external dependencies; I think I might use +this other approach in the future. + +The chat bot's response is great, but it is "only" 99% +accurate. Unfortunately this means that if one blindly follows these +instructions, they won't get a working QT application. The problem is +with the commands: + +``` +qmake -project +qmake +``` + +The first one is completely useless, and actually prevents the project +from being built by generating a second project file. If one simply +ignores the `qmake -project` line, the instructions become correct. + +This story is quite insightful: the chat bot gave me instructions that are +almost correct (so technically they are *incorrect*). With my experience +(a whole 5 hours of CV-worthy QT development) I was able to fix these +instructions easily in a couple of minutes, but someone without the same +experience would have probably struggled for much longer. diff --git a/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md b/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md @@ -17,8 +17,8 @@ than as fast as possible. Both the old and the new versions are written in C, but not all the things I discuss in this post are language-specific. You can find the new version of this project, the "Big Rewrite", -in [this repository](https://git.tronto.net/h48) - or -[on github](https://github.com/sebastianotronto/h48), if you prefer. +in [this repository](https://git.tronto.net/nissy-core) - or +[on github](https://github.com/sebastianotronto/nissy-core), if you prefer. I decided to rename it "H48" back when I planned to have separate projects for the different features of the old version, but I may change it back to Nissy at some point. diff --git a/src/blog/2025-06-06-webdev/blocking.png b/src/blog/2025-06-06-webdev/blocking.png Binary files differ. diff --git a/src/blog/2025-06-06-webdev/callback.jpg b/src/blog/2025-06-06-webdev/callback.jpg Binary files differ. diff --git a/src/blog/2025-06-06-webdev/callback2.jpg b/src/blog/2025-06-06-webdev/callback2.jpg Binary files differ. diff --git a/src/blog/2025-06-06-webdev/hello.png b/src/blog/2025-06-06-webdev/hello.png Binary files differ. diff --git a/src/blog/2025-06-06-webdev/logos.png b/src/blog/2025-06-06-webdev/logos.png Binary files differ. diff --git a/src/blog/2025-06-06-webdev/spectre.png b/src/blog/2025-06-06-webdev/spectre.png Binary files differ. diff --git a/src/blog/2025-06-06-webdev/storage.png b/src/blog/2025-06-06-webdev/storage.png Binary files differ. diff --git a/src/blog/2025-06-06-webdev/threads.jpg b/src/blog/2025-06-06-webdev/threads.jpg Binary files differ. diff --git a/src/blog/2025-06-06-webdev/wasm.png b/src/blog/2025-06-06-webdev/wasm.png Binary files differ. diff --git a/src/blog/2025-06-06-webdev/webdev.md b/src/blog/2025-06-06-webdev/webdev.md @@ -0,0 +1,1186 @@ +# A masochist's guide to web development + +## Table of contents + +* [Introduction](#introduction) +* [Setting things up](#setting-things-up) +* [Hello world](#hello-world) +* [Intermezzo I: What is WebAssembly?](#intermezzo-i-what-is-webassembly) +* [Building a library](#building-a-library) +* [Intermezzo II: JavaScript and the DOM](#intermezzo-ii-javascript-and-the-dom) +* [Loading the library and making it a module](#loading-the-library-and-making-it-a-module) +* [Multithreading](#multithreading) +* [Intermezzo III: Web Workers and Spectre](#intermezzo-iii-web-workers-and-spectre) +* [Don't block the main thread!](#dont-block-the-main-thread) +* [Callback functions](#callback-functions) +* [Persistent storage](#persistent-storage) +* [Closing thoughts](#closing-thoughts) + +## Introduction + +I have recently worked on making a web application out of +[my latest Rubik's cube optimal solver](https://git.tronto.net/nissy-core/file/README.md.html). +This involved building a rather complex C code base (with +multithreading, SIMD, callback functions and whatnot) to +[WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) via +[Emscripten](https://emscripten.org/), and writing a minimal amount of +JavaScript and HTML for the frontend. + +This whole process was complex, tiring and at times frustrating - +but eventually [it was a success](https://tronto.net:48)! Not only +I accomplished my goal, but I have learnt a lot along the way. After +finishing the work, I decided to write down all that I have learnt and +share it with the world with this post. + +You may be wondering why one should do such a thing instead of either +rewriting their code base in a more web-friendly language, or distributing +their app using a native GUI framework. The main reason to use WebAssembly +is that it can provide near-native performance (or so they claim) while +running inside a web browser; this gives you all the portability of a +web app without too much of a performance drawback, something that would +not be possible with an interpreted language such as JavaScript. + +So, what is this blog post? A tutorial for web development? I am not sure +about this, but if it is, it is definitely not a normal one. As the title +suggests, you should not start from this guide unless you just *love* +banging your head against the wall. If you are looking for a *sane* +guide to web development, I strongly advise you head on to the +[Mozilla Developer Network tutorials page](https://developer.mozilla.org/en-US/docs/MDN/Tutorials) +and start from there. + +But if you are a C or C++ developer looking to port a program or library +to the web, then you are in the right place. With this post I am going +to walk you through the process of building an increasingly complex +library that can run in a web browser. Make sure you are +sitting comfortably and be ready to sweat, because I am not going to +shy away from the hard stuff and the complicated details. + +To follow this tutorial you won't need much experience with web +development, but some familiarity with HTML and an idea of what JavaScript +will be useful. It will also help to know that you can access your +browser's JavaScript console and other developer tools by pressing F12, +at least on Firefox or Chrome - but I guess I have literally just taught +you that, if you did not already know it. For all the rest, I'll make +sure to add many hyperlinks throughout the text, so you can follow them +if something is new to you. + +A little disclaimer: although I am a somewhat experienced C developer, +I had very little web development experience before embarking in +this adventure. If you are a web developer, you may find errors in +this post that are going to make you laugh at my ignorance. If you do, +I'd appreciate it if you could report them to me by sending an email to +`sebastiano@tronto.net`! + +With this out of the way, let's get started! + +## Setting things up + +The examples used in this tutorial are all contained in a git repository, +which you can find either on +[my git page](https://git.tronto.net/emscripten-tutorial/file/README.md.html) or +[on github](https://github.com/sebastianotronto/emscripten-tutorial). + +In order to follow them you are going to need: + +* A working installation of [Emscripten](https://emscripten.org/) + (which also includes Node.js). Refer to the official website for + installation instructions. +* A web server such [darkhttpd](https://github.com/emikulic/darkhttpd) + or the Python `http.server` package; the examples will use darkhttpd. + +I have only tested all of this on Linux, but everything should work +exactly the same on any UNIX system. If you are a Windows user, you can +either run everything inside +[WSL](https://learn.microsoft.com/en-us/windows/wsl/), or you can try and +adjust the examples to your system - if you choose this second option, +I'll happily accept patches or pull requests :) + +## Hello world + +Let's start with the classic Hello World program: + +``` +#include <stdio.h> + +int main() { + printf("Hello, web!\n"); +} +``` + +You can compile the code above with + +``` +emcc -o index.html hello.c +``` + +And if you now start a web server in the current folder, for example with +`darkhttpd .` (the dot at the end is important), and open a web browser to +[localhost:8080](http://localhost:8080) (or whatever port your web server +uses), you should see something like this: + + + +As you can see, the compiler generated a bunch of extra stuff around +you print statement. You may or may not want this, but for now we can +take it as a convenient way to check that our program works as expected. + +There are other ways to run this compiled code. With the command above, +the compiler should have generated for you 3 files: + +* `index.html` - the web page in the screenshot above. +* `index.wasm` - the actual compiled code of your program; this file contains + WebAssembly bytecode. +* `index.js` - some JavaScript *glue code* to make it possible for `index.wasm` + to actually run in a browser. + +If you don't specify `-o index.html`, or if your specify `-o` followed +by a filename ending in `.js`, the `.html` page is not going to be +generated. In this case (but also if you *do* generate the html page), +you can run the JavaScript code in your terminal with: + +``` +node index.js +``` + +In later examples, the same code may not work seamlessly in both a web +browser and in Node.js - for example, when dealing with persistent data +storage. But until then, we can generate all three files with a single +command and run our code in either way. + +It is also possible to ask Emscripten to generate only the `.wasm` file, +in case you want to write the JavaScript glue code by yourself. To do +this, you can pass the `-sSTANDALONE_WASM` option to `emcc`. However, +in some cases the `.js` file is going to be generated even when this +option is used, for example when building a source file without a `main()` +entry point. Since this is something we'll do soon, we can forget about +this option and just take it as a fact that the `.wasm` files generated +by emscripten require some glue JavaScript code to actually run, +but in case you are interested you can check out +[the official documentation](https://emscripten.org/docs/tools_reference/settings_reference.html#standalone-wasm). + +You can find the code for this example, as well as scripts to +build it and run the web server, in the directory `00_hello_world` +of the git repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +Anyway, now we can build our C code to run in a web page. But this is +probably not the way we want to run it. First of all, we don't want to +use the HTML template provided by Emscripten; but more importantly, we +probably don't want to write a program that just prints stuff to standard +output. More likely, we want to write some kind of library of functions +that can be called from the front-end, so that the user can interact with +our program via an HTML + JavaScript web page. Before going into that, +let's take a break to discuss what we are actually compiling our code to. + +## Intermezzo I: What is WebAssembly? + + + +[WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) is a low-level +language meant to run in a virtual machine inside a web browser. The main +motivation behind it is running higher-performance web applications compared +to JavaScript; this is made possible, by its +compact bytecode and its stack-based virtual machine. + +WebAssembly (or WASM for short) is supported by all major browsers +since around 2017. Interestingly, Emscripten, the compiler we are +using to translate our C code to WASM, first appeared in 2011, +predating WASM by a few years. Early on, Emscripten would compile +C and C++ code into JavaScript, or rather a subset thereof called +[asm.js](https://en.wikipedia.org/wiki/Asm.js). + +Just like regular +[assembly](https://en.wikipedia.org/wiki/Assembly_language), WASM +also has a text-based representation. This means that one could write +WASM code directly, assemble it to bytecode, and then run it. We are +not going to do it, but if you are curious here is a simple example +(computing the factorial of a number, taken from Wikipedia): + +``` +(func (param i64) (result i64) + local.get 0 + i64.eqz + if (result i64) + i64.const 1 + else + local.get 0 + local.get 0 + i64.const 1 + i64.sub + call 0 + i64.mul + end) +``` + +As you can see, it looks like a strange mix of assembly and +[Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)). +If you want to try and run WASM locally, outside of a web browser, +you could use something like [Wasmtime](https://wasmtime.dev/). + +Until early 2025, the WASM "architecture" was 32-bit only. One big +limitation that this brings is that you cannot use more that 4GB +(2<sup>32</sup> bytes) of memory, because pointers are only 32 bits +long; moreover, your C / C++ code may need some adjustments if it +relied on the assumption that e.g. `sizeof(size_t) == 8`. At the +time writing a new standard that enables 64 bit pointers, called +WASM64, is supported on Firefox and Chrome, but not on Webkit-based +browsers such as Safari yet. Depending on when you are reading this, +this may have changed - you can check the status of WASM64 support +[here](https://webassembly.org/features/). + +## Building a library + +Back to the main topic. Where were we? Oh yes, we wanted to build +a C *library* to WASM and call it from JavaScript. Our complex, +high-performance, math-heavy library probably looks something like this: + +library.h (actually, we are not going to need this): + +``` +int multiply(int, int); +``` + +library.c: + +``` +int multiply(int a, int b) { + return a * b; +} +``` + +Or maybe it is a bit more complicated than that. But we said we are +going to build up in complexity, and this is just the beginning, so +let's stick to `multiply()`. + +To build this library you can use: + +``` +emcc -o library.js library.c +``` + +As we saw before, this is going to generate both a `library.js` and a +`library.wasm` file. Now we would like to call our library function +with something like this + +program.js: + +``` +var library = require("./library.js"); +const result = library.multiply(6, 7); +console.log("The answer is " + result); +``` + +*(The `require()` syntax above is valid when running this code in Node.js, +but not, for example when running in a browser. We'll see in the next +session what to do in that case, but for now let's stick to this.)* + +Unfortunately, this will not work for a couple of reasons. The reason +first is that Emscripten is going to add an underscore `_` to all our +function names; so we'll have to call `library._multiply()`. But this +still won't work, because by default the compiler does not *export* all +the functions in your code - that is, it does not make them visible to +the outside. To specify which functions you want to +export, you can use the `-sEXPORTED_FUNCTIONS` flag, like so: + +``` +emcc -sEXPORTED_FUNCTION=_multiply -o library.js library.c +``` + +And now we finally have access to our `multiply()` function... + +``` +$ node program.js +Aborted(Assertion failed: native function `multiply` called before runtime initialization) +``` + +...or maybe not. If you are new to JavaScript like I was a few weeks +ago, you may find this error message surprising. Some runtime must be +initialized, but can't it just, like... initialize *before* trying to +run the next instruction? + +Things are not that simple. A lot of things in JavaScript happen +*asynchronously*, and in these situations you'll have to either use +[`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) +or a +[*callback function*](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function). +So we'll have to do something like this: + +``` +var library = require("./build/library.js"); + +library.onRuntimeInitialized = () => { + const result = library._multiply(6, 7); + console.log("The answer is " + result); +}; +``` + +And now we can finally run our program: + +``` +$ node program.js +The answer is 42 +``` + +The code for this example can be found in the `01_library` folder in +the git repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +## Intermezzo II: JavaScript and the DOM + + + +If we want to build an interactive web page using JavaScript, we'll +need a way for our script to communicate with the page, i.e. a way +to access the HTML structure from JavaScript code. What we are looking +for is called +*[Document Object Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)*, +or DOM for short. + +For example, if you have a paragraph with some text in your HTML: + +``` +<p id="myParagraph">Hello!</p> +``` + +you can access this text from JavaScript like this: + +``` +var paragraph = document.getElementById("myParagraph"); +paragraph.innerText = "New text!"; +``` + +Here we are selecting the paragraph HTML element using its ID, and we +are changing its text via its `innerText` property, all from JavaScript. + +Let's see a more complex example: + +HTML: + +``` +<button id="theButton">Press me!</button> +``` + +JS: + +``` +var button = document.getElementById("theButton"); +var counter = 0; + +button.addEventListener("click", () => { + counter++; + button.innerText = "I have been pressed " + counter + " times!"; +}); +``` + +In the example above we add an +*[event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)* +to a button: the (anonymous) function we defined is going to be called +every time the button is clicked. And since this is a web page, I guess +I can show you what this actually looks like. + +Behold, the dynamic button: + +<div style="text-align:center"> +<button id="theButton">Press me!</button> +</div> + +<script> +window.onload = () => { + var button = document.getElementById("theButton"); + var count = 0; + + button.addEventListener("click", () => { + count++; + button.innerText = "I have been pressed " + count + " times!" + }); +}; +</script> + +If you are completely new to web development, you may be wondering +where you should write this JavaScript code. One option is to write it +in the same HTML file as the rest of the page, inside a `<script>` tag; +this is how I did it in the example above, as you can check by viewing +the source of this page (press Ctrl+U, or right-click and select +"view source", or prepend `view-source:` to this page's URL; hopefully +at least one of these methods should work in your browser). + +However, if the script gets too large you may want to split it off in +a separate file, which we'll demonstrate in this next example. + +Let's now make a template web page for using our powerful library. Let's +start with the HTML, which is in large part boilerplate: + +index.html: + +``` +<!doctype html> +<html lang="en-US"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width"> + <title>Multiply two numbers</title> + <script src="./script.js" defer></script> +</head> + +<body> + <p> + <input id="aInput"> x <input id="bInput"> + <button id="goButton">=</button> + <span id="resultText"></span> + </p> +</body> + +</html> +``` + +Besides the `<body>` element, the only important line for us is line +7, which loads the script from a file. Notice that we use the `defer` +keyword here: this is telling the browser to wait until the whole page +has been loaded before executing the script. If we did not do this, we +could run in the situation where we `document.getElementById()` returns +`null`, because the element we are trying to get is not loaded yet (yes, +this happened to me while I was writing this post). If you want to know +more, check out this +[MDN page](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#defer). + +Now to the JavaScript code. For now we are going to use the built-in +`*` operator to multiply the two numbers, but in the next section we +are going to replace it with our own library. + +script.js (in the same folder as index.html): + +``` +var aInput = document.getElementById("aInput"); +var bInput = document.getElementById("bInput"); +var button = document.getElementById("goButton"); +var resultText = document.getElementById("resultText"); + +button.addEventListener("click", () => { + var a = Number(aInput.value); + var b = Number(bInput.value); + resultText.innerText = a * b; +}); +``` + +The final result will look something like this: + +<p style="text-align:center"> +<input id="aInput"> x <input id="bInput"> +<button id="goButton">=</button> +<span id="resultText"></span> +</p> + +<script> +var aInput = document.getElementById("aInput"); +var bInput = document.getElementById("bInput"); +var button = document.getElementById("goButton"); +var resultText = document.getElementById("resultText"); + +button.addEventListener("click", () => { + var a = Number(aInput.value); + var b = Number(bInput.value); + resultText.innerText = a * b; +}); +</script> + +In a real-world scenario you would probably want to check that the text +provided in the input fields is actually a number, or perhaps use the +[`type="number"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number) +attribute for the input fields. But we'll ignore these issues here - +we are going to have more serious problems to deal with. + +## Loading the library and making it a module + +With what we have learned in the previous intermezzo (you are not skipping +those, right?) we can finally run our library code in a real web page. The +code is pretty much the same as above; we just need to include both the +library and the script file in the HTML: + +``` + <script src="./library.js" defer></script> + <script src="./script.js" defer></script> +``` + +and of course we have to change the line where we perform the multiplication: + +``` + resultText.innerText = Module._multiply(a, b); +``` + +Here `Module` is the default name given to our library by +Emscripten. Apart from being too generic a name, this leads to another +problem: we can't include more than one Emscripten-built library in our +page in this way - otherwise, both are going to be called `Module`. + +Luckily, there is another way: we can build a +[modularized](https://emscripten.org/docs/compiling/Modularized-Output.html) +library, i.e. obtain a +[JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). +This may sound a bit strange, because the name `Module` kind of implies +there is already a module. The way I understand it is that by default +Emscripten produces a *script* that *contains* a module named `Module`; +when building a modularized library, the whole resulting file is a module. + +Modularizing our build is not necessary right now, but +there are a couple of other advantages to it: + +* As mentioned above, we can change the name of our module and include + more than one Emscripten-built library, if we want. +* We will be able to use the module in the same way in Node.js and in + our web page script. This way we can minimize the differences between + the two versions of our code, which can be useful for testing. +* In case we want to build a more complex layer of JavaScript between + our library and our web page, with a modularized build we can easily + include the module in another file, which can then be included in the + main script. + +So let's go ahead and build our library like so: + +``` +emcc -sEXPORTED_FUNCTION=_multiply -sMODULARIZE -sEXPORT_NAME=MyLibrary \ + -o library.mjs library.c +``` + +Notice I have changed the extension from `.js` to `.mjs`. Don't worry, +either extension can be used. And you are going to run into issues with +either choice: + +* If you run your code in Node.js, it will understand that the library + file is a module only if you use the `.mjs` extension. Alternatively, + you can change some settings in a local configuration file to + enforce this. +* If you run your code in a web page, your web server may not be + configured to serve `.mjs` files as JavaScript files. This can + easily be changed by adding a configuration line somewhere. + +In my examples I chose to use the `.mjs` extensions to make Node.js +happy, and I changed the configuration of my web servers as needed. For +example, for darkhttpd I added a file called `mime.txt` with a single +line `text/javascript mjs`, and launched the server with the +`--mimetypes mime.txt` option. + +Now we have to make a couple of changes. Our `program.js`, for running +in node, becomes: + +``` +import MyLibrary from "./library.mjs" + +var myLibraryInstance = await MyLibrary(); + +const result = myLibraryInstance(6, 7); +console.log("The answer is " + result); +``` + +By the way, I have renamed this file to `program.mjs`. This is because +only modules can use the +[static `import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) +statement; alternatively, I could have used the +[dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) +and kept the `.js` extension. + +Similary, we have to update our `script.js` (or `script.mjs`) to import +the module and create an instance. Moreover, we have to specify in the +HTML that the script is now a module: + +``` + <script src="./script.mjs" type="module" defer></script> +``` + +And we can get rid of the other `<script>` tag, since now the library +is included directly in `script.mjs`. + +You can find the full the code for this example the folder +`02_library_modularized` in the git repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +## Multithreading + + + +Let's move on to a more interesting example. If one of the goals of +WebAssembly is performance, there is no point in using only 1/16th of +your CPU - let's port a multithreaded application to the web! + +As a more complicated example, let's write a function that counts how +many prime numbers there are in a given range. This function takes two +integers as input and returns a single integer as output, but it does +a non-trivial amount of work under the hood. A simple implementation of +this routine would be something like this: + +``` +bool isprime(int n) { + if (n < 2) + return false; + + for (int i = 2; i*i <= n; i++) + if (n % i == 0) + return false; + return true; +} + +int primes_in_range(int low, int high) { + if (low < 0 || high < low) + return 0; + + int count = 0; + for (int i = low; i < high; i++) + if (isprime(i)) + count++; + + return count; +} +``` + +This algorithm is +[embarassingly parallelizable](https://en.wikipedia.org/wiki/Embarrassingly_parallel): +we can split the interval `[low, high)` into smaller sub-intervals and +process each one of them in a separate thread; then we just need to add +up the results of the sub-intervals. + +For the actual implementation, we are going to use +[pthreads](https://en.wikipedia.org/wiki/Pthreads), for the simple reason +that it is +[supported by Emscripten](https://emscripten.org/docs/porting/pthreads.html). +In practice, assuming we are working on a UNIX platform, we could also +use C11's [threads.h](https://en.cppreference.com/w/c/header/threads) or +C++'s [std::thread](https://en.cppreference.com/w/cpp/thread/thread.html), +but only because they happen to be wrappers around pthreads. On other +platforms, or in other implementations of the C and C++ standard library, +this may not be the case; so we'll stick to old-school pthreads. + +This is my parallel version of `primes_in_range()`: + +primes.c: + +``` +#include <stdbool.h> +#include <pthread.h> + +#define NTHREADS 16 + +bool isprime(int); +void *pthread_routine(void *); + +struct interval { int low; int high; int count; }; + +int primes_in_range(int low, int high) { + pthread_t threads[NTHREADS]; + struct interval args[NTHREADS]; + + if (low < 0 || high < low) + return 0; + + int interval_size = (high-low)/NTHREADS + 1; + for (int i = 0; i < NTHREADS; i++) { + args[i].low = low + i*interval_size; + args[i].high = args[i].low + interval_size; + pthread_create(&threads[i], NULL, pthread_routine, &args[i]); + } + + int result = 0; + for (int i = 0; i < NTHREADS; i++) { + pthread_join(threads[i], NULL); + result += args[i].count; + } + + return result; +} + +bool isprime(int n) { + if (n < 2) + return false; + + for (int i = 2; i*i <= n; i++) + if (n % i == 0) + return false; + return true; +} + +void *pthread_routine(void *arg) { + struct interval *interval = arg; + + interval->count = 0; + for (int i = interval->low; i < interval->high; i++) + if (isprime(i)) + interval->count++; + + return NULL; +} +``` + +*(Pro tip: if you take the number of threads as an extra parameter for +your function, you can pass to it the value +[`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency) +from the JavaScript front-end and use exactly the maximum number of +threads that can run in parallel on the host platform.)* + +To build this with Emscripten we'll have to pass the `-pthread` option and, +optionally, a suitable value for +[`-sPTHREAD_POOL_SIZE`](https://emscripten.org/docs/tools_reference/settings_reference.html#pthread-pool-size). + +If we want to run our multithreaded code in an actual browser, we'll +have to scratch our head a bit harder. The code we are supposed to +write is exactly what we expect, but once again we have to tinker with +our web server configuration. For technical reasons that we'll cover in +the next intermezzo, in order to run multithreaded code in a browser +we must add a couple of HTTP headers: + +``` +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +``` + +These headers are part of the response your browser will receive when +it requests any web page from the server. The way you set these depends on +the server you are using; with darkhttpd you can use the `--header` option. + +With your server correctly set up, you can enjoy a multithreaded program +running in your browser! As always, you can check out this example from +the `03_threads` folder of the git repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +## Intermezzo III: Web Workers and Spectre + + + +On a low level, threads are implemented by Emscripten using +[web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), +which are processes separated from the main web page process and +communicate with it and with each other by +[passing messages](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage). +Web workers are commonly used to run slow operations in the background +without blocking the UI threads, so the web page remains responsive +while these operations run - we'll do this in the next section. + +Web workers do not have regular access to the same memory as the main +process, and this is something that will give us some issues in later +sections. However, there are ways around this limitation. One of these +ways is provided by +[SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), +which we won't use directly in this tutorial, but is used by +Emscripten under the hood. + +And this is why we had to set the `Cross-Origin-*` headers. In 2018, a +CPU vulnerability called [Spectre](https://spectreattack.com) was found, +and it was shown that an attacker could take advantage of shared memory +between the main browser thread and web workers to +[execute code remotely](https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)#Remote_exploitation). +As a counter-measure, most browsers now require your app to be in a +[secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) +and +[cross-origin isolated](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated) +to allow using `SharedArrayBuffer`s. + +Even if you do not plan to use web workers directly, it is still good to +have a rough idea of how they work, because of the +[law of leaky abstractions](https://en.wikipedia.org/wiki/Leaky_abstraction): +*all abstractions are leaky*. +The fact that we had to mess around with our `Cross-Origin-*` headers +despite not caring at all about `SharedArrayBuffer`s is a blatant example +of this. + +## Don't block the main thread! + +If you have run the previous example, may have noticed a scary warning +like this in your browser's console: + +![A warning saying "Blocking on the main thread is very dangerous, see [link]"](blocking.png) + +*The link points to +[this page](https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread) +in Emscripten's documentation.* + +The issue here is that our heavy computation is not running "in the +background", but its main thread (the one spawning the other threads) +coincides with the browser's main thread, the one that is responsible +for drawing the UI and handling user interaction. So if our computation +really takes long, the browser is going to freeze - and after a few +seconds it will ask us if we want to kill this long-running script. + +As we anticipated in the previous intermezzo, we are going to solve this +with a web worker. We will structure this solution as follows: + +* The main script will be responsible for reading the user input, sending + a message to the worker to ask it to compute the result, and handling + the result that the worker is going to send back once it is done. No + slow operation is performed by this script, so that it won't block + the main thread. +* The worker will be responsible for receiving mesages from the main + script, handling them by calling the library, and sending a message + with the response back once it is done computing. + +In practice, this will look like this: + +script.mjs: + +``` +var aInput = document.getElementById("aInput"); +var bInput = document.getElementById("bInput"); +var button = document.getElementById("goButton"); +var resultText = document.getElementById("resultText"); + +var worker = new Worker("./worker.mjs", { type: "module" }); + +button.addEventListener("click", () => worker.postMessage({ + a: Number(aInput.value), + b: Number(bInput.value) +})); + +worker.onmessage = (e) => resultText.innerText = "There are " + + e.data.result + " primes between " + e.data.a + " and " + e.data.b; +``` + +worker.mjs: + +``` +import Primes from "./build/primes.mjs"; + +var primes = await Primes(); + +onmessage = (e) => { + const count = primes._primes_in_range(e.data.a, e.data.b); + postMessage({ result: count, a: e.data.a, b: e.data.b }); +}; +``` + +More complicated than before, but nothing crazy. Notice how we are using +[`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) +and +[`onmessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event) +to pass events back and forth. The argument of `postMessage()` is the +actual data we want to send in +[JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) +format, while the argument of `onmessage()` is an +[event](https://developer.mozilla.org/en-US/docs/Web/API/Event) +whose `data` property contains the object that was sent with `postMessage()`. + +You can check out this example in the directory `04_no_block` in the +repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). +Try also large numbers, in the range of millions or tens of millions, and +compare it with the previous example - but not don't go too large, we +only support 32-bit integers for now. Notice how, with this new setup, +the browser remains responsive while it is loading the response. + +Oh and by the way, a nice exercise for you now could be making the +script show some kind of `"Loading result..."` message while the worker +is working. This is not hard to do, but a huge improvement in user +experience! + +## Callback functions + + + +For one reason or another, your library function may take as parameter +another function. For example, you may use this other function to print +log messages regardless of where your library code is run: a command-line +tool may pass `printf()` to log to console, while a GUI application +may want to show these messages to some text area in a window, and it +will pass the appropriate function pointer parameter. This is the use case +that we are going to take as an example here, but it is not the only one. + +Implementing this was probably the step that took me the longest in my +endeavor to port my Rubik's cube solver to the web. Luckily for you, +when writing this post I found a simpler method, so you won't have to +endure the same pain. + +First, we'll have to adapt our library function like this: + +``` +int primes_in_range(int low, int high, void (*log)(const char *)) { + /* The old code, with calls to log() whenever we want */ +}; +``` + +*Tip: when using callback functions like this, it is good practice +to have them accept an extra `void *` parameter, and the library +function should also accept an extra `void *` parameter that it then +passes on to the callback. So our function would look something like +this: `int primes_int_range(int low, int high, void (*log)(const char *, void *), void *log_data)`. +This makes the setup extremely flexible, and allows passing callback +functions in situation where this may be tricky. For example, this +way you could pass a C++ member function by passing an object as +`log_data` and a function that call `log_data`'s member function +as `log`. Since we are not going to use this in this example, I'll stick +to the simpler setup.* + +Now, to call our function from the JavaScript side we would like +to do something like this: + +``` +int result = primes_in_range(a, b, console.log); // Logging to console +``` + +Unfortunately, this will not work, because `console.log`, a JavaScript +[function object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function), +does not get automatically converted to a function *pointer*, which is +what C expects. So we'll have to do something slightly more complicated: + +``` +import Primes from "./build/primes.mjs" + +var primes = await Primes(); +const logPtr = primes.addFunction((cstr) => { + console.log(primes.UTF8ToString(cstr)); +}, "vp"); + +const count = primes._primes_in_range(1, 100, logPtr); +``` + +Here `addFunction()` is a function generated by Emscripten. Notice also +that we are wrapping our `console.log()` in a call to `UTF8ToString()`, +an Emscripten utility to convert C strings to JavaScript strings, and +that we are passing the function's signature `"vp"` (returns `void`, +takes a `pointer`) as an argument; see +[here](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#function-signatures) +for more information. + +Other than that, you just need to add a couple of compiler flags: + +* `-sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString` to tell the + compiler to make these two methods available. +* `-sALLOW_TABLE_GROWTH` to make it possible to add functions to + out module at runtime with `addFunction()`. + +And as you can check by running the example `05_callback` from the repo +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)), +everything works as expected, both in Node.js and in a web page. To make +the examples more interesting, the web page one is not only not logging the +messages to console, but it also shows them as text in the web page. + +*Note: you must be careful where you call this callback function from. +If you try to call it from outside the main thread - for example, in one +of the threads that are spawned to count the primes in the sub-intervals +- you'll get a horrible crash. This is because web workers do not have +access to the functions that reside in another worker's memory.* + +## Persistent storage + + + +Our multithreaded implementation of `primes_in_range()` is not slow, but +it could be faster. One possible way to speed it up is to use a look-up +table to make `is_prime()` run in constant time; for this we'll need to +memorize which numbers below 2<sup>31</sup> (the maximum value of 32-bit +signed integer) are prime. This will require 2<sup>31</sup> bits of data, +or 256MB. It would be nice if we could store this data persistently in +the user's browser, so that if they use our app again in the future we +won't need to repeat expensive calculations or re-download large files. + +Putting aside the question of whether any of the above is a good idea, +and assuming you know how to generate such a table, in C you would +read and store the data like this: + +``` +#include <stdio.h> + +#define FILENAME "./build/primes_table" + +void read_table(unsigned char *table) { + FILE *f = fopen(FILENAME, "rb"); + fread(table, TABLESIZE, 1, f); + fclose(f); +} + +void store_table(const unsigned char *table) { + FILE *f = fopen(FILENAME, "wb"); + fwrite(table, TABLESIZE, 1, f); + fclose(f); +} +``` + +*Note: the code snippet above is extremely simplified, you probably want +to add some error-handling code if you implement something like this.* + +The good news is that we can use the same code when building with +Emscripten! The bad news is that... well, it's a bit more complicated +than that. + +First of all, it is important to know that +[Emscripten's File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html) +supports different "backends", by which I mean ways of translating the +C / C++ file operations to WASM / JavaScript. I am not going to discuss +all of them here, but I want to highlight a few key points: + +* The default backend is called `MEMFS`. It is a virtual file system + that resides in RAM, and all data written to it is lost when the + application is closed. +* Only one of these backends (`NODERAWFS`) gives access to the actual + local file system, and it is only usable when running your app with + Node.js. Browsers are *sandboxed*, and the filesystem is not normally + accessible to them. There are ways, such as the + [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API), + to access files, but as far as I understand each file you want to + access requires explicit actions from the user. We would like to manage + our data automatically, so we are not going to use this API. +* The backend we are going to use is called `IDBFS`. It provides access + to the [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), + which allows to persistently store large quantities of data in the + browser's cache. The data is only removed if the user asks for it, + for example by cleaning it from the browser's settings page. + +To activate the `IDBFS` backend, we are going to add `--lidbfs.js` +to our compiler options. The Indexed DB is not the only way to store +data persistently in the browser. For an overview of all the options, +you can take a look at +[this page on MDN](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Client-side_storage). + +The compiler flag is not enough, however. We also need to: + +1. Create a directory (for the virtual file system) where our data file + is going to be stored. We are going to call this directory `assets`, + but you can pick any other name; it does not have to coincide with the + name of a directory that exists on your local file system. +2. Mount the directory we have just created in the indexed DB. +3. Synchronize the virtual file system, so that our script is able to + read pre-existing files. + +All of the above has to be done from JavaScript, which makes things a +little bit complicated, because we are reading our files from C code. +We have a couple of ways to work around this issue: + +* Using + [inline JavaScript](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-javascript-from-native) + in our C code with the `EM_JS()` or `EM_ASYNC_JS()` Emscripten macros. +* Setting up the indexed DB file system when the module loads using + the `--pre-js` compiler option. + +Here we are going to use the second solution, but the first option is +good to keep in mind, because it allows us to call JavaScript code at +any point rather than just at startup. + +*Note: if you do end up using `EM_ASYNC_JS()` to make asynchronous +JavasScript functions callable from C, keep in mind that any C +function that, directly or indirectly, calls an async JavaScript +function, will now return a +[promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +when called from JavaScript. But wether an async function is called is +determined at runtime, so you C function may return a value one time +and a promise another time, depending on how exactly it runs!* + +So we are going to add `--pre-js init_idbfs.js` to our compiler options, +with `init_idbfs.js` containing the following: + +``` +Module['preRun'] = [ + async () => { + const dir = "/assets"; + + FS.mkdir(dir); + FS.mount(IDBFS, { autoPersist: true }, dir); + + Module.fileSystemLoaded = new Promise((resolve, reject) => { + FS.syncfs(true, (err) => { + if (err) reject(err); + else resolve(true); + }); + }); + + } +]; +``` + +As you can see, the syncing operation is more complicated, the main +reason being that it is an +[asynchronous operation](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Async_JS). +For this reason, we are wrapping it in a +[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), +so we can detect when this operation is done and react accordingly. +We are going to do so from our worker script, which will send a message to +the main script to communicate that the file system is ready to go: + +``` +primes.fileSystemLoaded.then(() => { + postMessage({ type: "readySignal" }); +}); +``` + +The main script can then handle this signal as it prefers, for example by +enabling the `Compute` button, if it was previously marked as `disabled`. + +One last thing: since we are now using a large amount of memory and +loading the virtual file system at the start, the compiler will complain +that we are not reserving enough memory for our application. Adding a +`-sINITIAL_MEMORY=272629760` compiler flag will do the trick (watch out: +the number you provide must be a multiple of 2<sup>16</sup>). I am not +entirely sure why this is the case, since we are not loading the file in +memory statically, but only at runtime, and only when the +`primes_in_range()` function is called. I would expect that using +[`-sALLOW_MEMORY_GROWTH`](https://emscripten.org/docs/tools_reference/settings_reference.html#allow-memory-growth) +would be enough - and indeed this is the case if we use the `EM_ASYNC_JS()` +macro to load the file system on-demand. + +And with all this, we are ready to run our optimized version of the +`primes_in_range()` algorithm, all from within our browser! As always, +you can check out the complete code in the folder `06_storage` of +the repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +If generating this data on the user's side seems redundant, you can +also have it downloaded from the server. I won't explain how to it here, +since there are many possible ways to achieve this - after all, the indexed +DB is also accessible from JavaScript. If you want to experiment more +with Emscripten you can try to use the +[Fetch API](https://emscripten.org/docs/api_reference/fetch.html); in my +project I was not able to make its synchronous version work together with +`-sMODULARIZE`, so I ended up using +[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) +directly from within an `EM_ASYNC_JS()` function. This tutorial is already +too long, so I am going to leave this as an exercise for the reader. + +## Closing thoughts + +I have discussed almost everything that I have learned about building a +webapp in C / C++ with Emscripten. I ended up using C, not C++, for all +of my example, so I did not have a chance to discuss some neat C++-specific +features such as +[`EMBIND()`](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html) +and +[`emscripten::val`](https://emscripten.org/docs/api_reference/val.h.html) +- do check them out if you plan to use C++ for your web app! + +Even if this page is structured like a tutorial, this is probably better +described as a collection of personal notes, a "brain dump" that I wrote +for myself as is the case with many of my blog posts. Writing this piece +was a great occasion for me to review the work that I have done and the +things I have learned. And while reflecting on all of this I was able to +isolate a specific impression that I had while working on this, +and I summarized it in on sentence: + +<center><strong><i> +It's leaky abstractions all the way down. +</i></strong></center> + +If you have not encountered this term before (but you should, I have already +used it in this post), *leaky abstraction* is a term used to describe the +failure of an abstraction to hide the low-level details it is abstracting. +The so-called +[law of leaky abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/) +says that all abtractions are leaky. But, in my opinion not all +abstractions leak in the same way - some leak way more than others. + +Emscripten is a great project that tries to abstract away all the web +(JavaScript, WASM, web workers, local storage...) so that you can build +and run your C / C++ code in a web browser. Frankly, this is mind-blowing, +and I have mad respect for the Emscripten developers. + +But as soon as the complexity of your codebase bumps up a notch, you +immediately find out that the abstractions don't hold anymore. If yor +app is multithreaded, you have to learn what a web worker is. If you +want to read some data from a file, welcome to the world of client-side +storage. You need 64-bit memory support because you are processing more +than 2GB of data? Sure, but first make sure that your users are not +using Safari. + +But I am not complaining about this. A browser is a very different beast +from a bare-metal operating system, and it is to be expected that you +have to know something about the system you are deploying to. I am happy +that I could learn about all of this, and I believe this knowledge is +going to give me an extra edge whenever I'll work on the web again. diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md @@ -0,0 +1,332 @@ +# Cargo culture shock 🦀 + +After a long adventure +[porting my cube solver to the web](../2025-06-06-webdev), I decided to +try out something completely different, like learning a new language. +Rust is one on my to-do list, and it has been there for more than 10 +years - I remember reading about it in my first year in university, +so it must have been 2013 or early 2014. + +So I started by quickly reading through the first few chapters of +[the book](https://doc.rust-lang.org/book/) to get an idea of the basic +syntax. Before I move on to implementing something, I thought I could +share my very early impression of the language and the tooling around it. + +*Note: this is a relaxed write-up and I have a very superficial +understanding of the topic. I am going to use strong words for the things +that I did not like, but there are many things I like about Rust so +far. In fact, my first impression of the language, the documentation and +the tools is very positive! Keep this in mind while reading this post.* + +## What is Rust about? + +Before getting started with the Rust book, my impression was that Rust +was mostly about *safety* as in *memory safety*, and that it achieved +this by enforcing strict rules, leading to more correct programs overall. + +But [the foreword](https://doc.rust-lang.org/book/foreword.html) says "the +Rust programming language is fundamentally about *empowerment*". Uh, that's +weird. I was expecting "something something *safety*". And actually, +I was hoping for "something something *correctness*". Definitely not +"something something *empowerment*". *Empowerment* sounds like one of +those meaningless words that managers use when they have nothing to say. + +Anyway, empowerement it is. Let's move on to actually using the thing. + +## Installation + +The officially endorsed way of installing Rust is the following: + +``` +curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh +``` + +The horror, the horror! Is this really what they suggest? The very thing +that *everyone* told you not to do in nerd forums until a few years ago, +even worse than copy-pasting commands from the internet, is now THE +suggested way of installing software? Seriously, piping a random web +page into `sh`? + +I don't know what this script does, is it going to download even more +random shit from the internet? (Answer: yes, it is!) Is it going to fuck +up my environment by puking configuration lines into my .bashrc? (Answer: +yes, it is!) + +But ok, what do I know. Maybe I am just living in the past, I should +embrace the future, because package managers and `make && make install` +*are so 1999, man*. So I took a deep breath an run the command. +Luckily the script kindly asked "do you want me to fuck up your +environment? [default: Yes]". I may be paraphrasing this, it probably +mentioned `.bash_rc` and `.profile`. Anyway, you can easily say "no" +and skip this fuckery. And then manually update a couple of environment +variables in your shell configuration file, which is what the script was +trying to do. I guess developers today are not expected to know how to +do this. + +The installation was quick and everything went fine. Moving on! + +## Cargo cult + +[Cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) is +Rust's build system and all-in-one tool. It can create a new project +with a default folder structure, compile code (running `rustc` under +the hood`), run tests, and more. Oh and it is also a *package manager*, +which apparently is something modern programming languages decided they +needed - more on that later. + +I don't always like these kind of tools, becase they tend to do a lot of +things that you don't understand with the files in the current directory. +Like vomiting generated files that you are supposed to check into your +[VCS](https://en.wikipedia.org/wiki/Version_control) - which kinda +defeats the purpose of *generating* files in the first place, doesn't it? + +Luckily, Cargo does not do too much of this. It generates an `src` folder +with a simple "hello world" program, a pretty minimal `Cargo.toml` +configuration file, and it initializes a git repo. All reasonable, +and most importantly easy to understand. + +On the first run of `cargo build` it also generates a `target` folder +for the build artifacts (already in `.gitignore`) and it generates +only one of those vomit files that I am supposed to check into git +(`Cargo.lock`), which I promptly added to .gitignore. Apparently it is +mainly about dependencies, and I'd rather not use any for now. So all +in all I am satisfied with this process. + +About dependencies, you can specify some packages, or *crates*, that your +projects depends on. These *crates* are going to be downloaded as they are +needed from [crates.io](https://crates.io/). You can of course specify +some constraints on the version for each specific crate, like "at least +2.0" or "at least 0.8.5, but less than 0.9.0". Here is where `Cargo.lock` +comes into play: if there is more than one version available for a given +crate that satisfies the contraints you impose in `Cargo.toml`, the first +time a specific version is used it gets written to `Cargo.lock`, so that +you will keep using that version even if a new one becomes available. +You are supposed to check in this file to git so other developers working +on the same project will use exactly the same version of each dependency. + +I can't quite understand this. If I don't want to update to a later +(minor) version of the dependency, I can already specify this in +`Cargo.toml`. If instead I impose more relaxed requirements, then it +means that any version satisfying those requirements is fine, and I +in this case *I do want* to try different versions within the allowed +range, to make sure that my assumptions are correct. So I am confused +about why this Cargo.lock business is needed at all. But I guess if +the time ever comes that I need to include dependencies in my project - +probably in the far future, *right?* - then I can just specify an exact +version and happily ignore `Cargo.lock`. + +Anyway, coming from a mostly C and C++ background where there is no +standard way of including dependencies, all of this is certainly quite +interesting. + +## Dependencies? + +Chapter 2 contains a simple code example. +[This part](https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#generating-a-secret-number) +caught my attention: + +"Rust doesn't yet include random number functionality in its standard library." + +Uh, ok. And then people complain about C having a small standard library. +It then continues: + +"However, the Rust team does provide a +[`rand` crate](https://crates.io/crates/rand) with said functionality." + +Ok, but... if this is provided by the Rust team, why not including it in +the standard library? Maybe it is still in beta, Rust is not a "finished" +project as far as I understand. I would think 15 years is enough to ship +a random number generator, though. + +The tutorial tell us to add `rand` to our dependencies in `Cargo.toml`. +Simple enough. But then, if we launch `cargo build` again: + +``` +cargo build + Updating crates.io index + Locking 15 packages to latest Rust 1.85.0 compatible versions + Adding rand v0.8.5 (available: v0.9.0) + Compiling proc-macro2 v1.0.93 + Compiling unicode-ident v1.0.17 + Compiling libc v0.2.170 + Compiling cfg-if v1.0.0 + Compiling byteorder v1.5.0 + Compiling getrandom v0.2.15 + Compiling rand_core v0.6.4 + Compiling quote v1.0.38 + Compiling syn v2.0.98 + Compiling zerocopy-derive v0.7.35 + Compiling zerocopy v0.7.35 + Compiling ppv-lite86 v0.2.20 + Compiling rand_chacha v0.3.1 + Compiling rand v0.8.5 + Compiling guessing_game v0.1.0 (file:///projects/guessing_game) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s +``` + +Woho wait a minute. I have only added one dependency! Just a random +number generator! Why is it building 15 packages now? Where does all +of this come from? Surely these are not dependencies dragged in by +the `rand` crate, *right?* + +Well, apparently they are. If we want to generate a random number, we +need 15 external packages. I would imagine this makes Rust completely +unusable in a professional setting, because nobody would want to audit +15 separate crates just to include a random number generator. And no +professional programmer would include dependencies in a serious project +without first auditing them, *right?* *RIGHT?!* + +I mean, each dependency introduces an extra liability in your project; +it is code that you can't control and you never know when it is going to +break. And this is why serious programmers, select dependencies carefully +and only use them when absolutely necessary... *right?* + +Ahah, of course they do not. Remember the +[letf-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident)? +Apparently Cargo is of the same breed as npm. And then we wonder why +the software industry is such a shitshow. *Just keep building on top +of the house of cards, man. It's going to be fine, man, don't bother +implementing low-level stuff, that's hard. Just trust random people on +the internet and ship their code, man.* + +Bleah. + +## The Rust language + +So far I talked mostly about the tooling around Rust, but I said nothing +about the language itself. I want to write some code before making a +more informed opinion about it, but the first impression is that it is +really nice! + +I love the type system, especially +[enums](https://doc.rust-lang.org/book/ch06-00-enums.html) +and `match`. I like that this and stuff like `Option<T>` are +first-class citizens; you can do something similar in C++ with +[`std::variant`](https://doc.rust-lang.org/book/ch06-00-enums.html) and +[`std::optional`](https://en.cppreference.com/w/cpp/utility/optional.html), +but the syntax quickly gets messy. I guess this shows the advantage of +making a new language from scratch instead of being forced to live with +decades-old syntax for backwards compatibility. + +I especially like this mechanism - Rust's enums, or `std::variant`, or +tagged unions, whatever you like to call them - for error handling. I find +them a better solution than exception, because they enforce correctness: +if your function can fail, your coding must handle it; you can't just +let an error message bubble up from the depths of Hell, wreak havoc in +your control flow and face the user with an "Object reference not set +to an instance of an object". I think it is great that Rust endorses +this more thorough way of error handling from the start. + +On the less-nice side of things, apparently +[integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow) +is still an issue. Eh. At least when compiling in debug mode overflows +are caught, which is nice. But in release mode the program is +going to "panic", which I was not expecting. I mistakenly thought +this was one of the issues that Rust was solving at compile time, +or maybe even at the level of language specification. + +This brings me to another problem that I thought Rust would solve, +but it does not: accessing a out-of-bound index of an array also leads +to a panic. I was expecting "don't allow indexes out of bound" to be +included in Rust's compile-time checks, somehow. For example in Ada you +can declare an array to accept only values of a specific +[range type](https://en.wikibooks.org/wiki/Ada_Programming/Types/range), +so that out-of-bound errors are completely eliminated. Apparently a +"panic" is memory safe behavior, but it is definitely not *correct* +behavior. I guess this was a big misunderstanding from my side: memory +*safety* does not mean memory *correctness* - Rust still allows you to +make mistakes related to memory. + +## Documentation + +A quick note on the documentation: it is very nice. The book is well +written, and I am enjoying reading through it, even though it is very +basic for me. It is also available offline - at least when installing +rust via `rustup` - which is something I always appreciate. + +The compiler error messages, which I consider to be part of the +documentation as well, are simply amazing. Not only they are usually very +precise, not only they often suggest a fix, but they also point you out to +some piece of documentation so that you can learn why your code is wrong. +For example, if you try to compile this code with `rustc filename.rs`: + +``` +fn main() { + println("Hello, world!"); +} +``` + +You get: + +``` +error[E0423]: expected function, found macro `println` + --> hello.rs:2:2 + | +2 | println("Hello, world!"); + | ^^^^^^^ not a function + | +help: use `!` to invoke the macro + | +2 | println!("Hello, world!"); + | + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0423`. +``` + +And if you run `rustc --explain E0423` as suggested: + +``` +An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace. + +Erroneous code example: + +struct Foo { a: bool }; + +let f = Foo(); +// error: expected function, tuple struct or tuple variant, found `Foo` +// `Foo` is a struct name, but this expression uses it like a function name + +Please verify you didn't misspell the name of what you actually wanted to use here. Example: + +fn Foo() -> u32 { 0 } + +let f = Foo(); // ok! + +It is common to forget the trailing ! on macro invocations, which would also yield this error: + +println(""); +// error: expected function, tuple struct or tuple variant, +// found macro `println` +// did you mean `println!(...)`? (notice the trailing `!`) + +Another case where this error is emitted is when a value is expected, but something else is found: + +pub mod a { + pub const I: i32 = 1; +} + +fn h1() -> i32 { + a.I + //~^ ERROR expected value, found module `a` + // did you mean `a::I`? +} +``` + +I would not be surprised if the main reason why Rustaceans are so +enthusiastic about the language was actually how nice `rustc` is. +I expect working with this tool will be very pleasant. + +## Moving on! + +Apart from the +[culture shock](https://en.wikipedia.org/wiki/Culture_shock) of the +installation process and the dependency management, my first impression +of Rust is quite positive. But as I said, I am just getting started +and my judgement is very superficial. I do want to write some small +project in Rust, and I think I'll start from re-writing a simple +[math library for modular arithmetic](../2025-01-21-taming-cpp-templates/#constraints-and-concepts) +that I wrote in C++ some time ago. + +Stay tuned for more 🦀 diff --git a/src/blog/2025-06-26-borrow-checker/borrow-checker.md b/src/blog/2025-06-26-borrow-checker/borrow-checker.md @@ -0,0 +1,150 @@ +# Stunned by the borrow checker 🦀 + +As I mentioned in my [last post](../2025-06-13-cargo-culture-shock), +in the last couple of weeks I have been learning Rust. I have written +[a small library](https://git.tronto.net/zmodn-rs/file/README.md.html) for +[integers modulo N](https://en.wikipedia.org/wiki/Modular_arithmetic) +(the original C++ version was mentioned in +[this post](../2025-01-21-taming-cpp-templates), rewritten +[my implementation](https://git.tronto.net/ecm/file/README.md.html) +of the +[ECM algorithm](https://en.wikipedia.org/wiki/Lenstra_elliptic-curve_factorization) +(mentioned in [this other post](../2025-02-27-elliptic-curves-javascript)) +and I am now playing around with some past +[Advent of Code](https://adventofcode.com/) problems. + +But today I won't talk about any of the above. Instead, I just want to show +you a small example of code that kept me confused for a couple of hours. + +First, I need to very briefly explain Rust's concept of ownership. + +## The borrow checker + +The *borrow checker* is a unique feature of Rust that prevents certain +kinds of memory errors and data races. Without going into too much +detail, the borrow checker is a compile-time mechanism that ensures that, +at any given point, a given object is owned by at most one reference, +unless all references to it are *immutable* (that is, they don't allow +modifying the object). + +As a simple example, the following code is not valid: + +``` +fn main() { + let mut x = 2; // mut means mutable, without it x would be a constant + let y = &x; // & means reference + x = 3; + println!("{x} {y}"); +} +``` + +And the compiler gives a clear explanation: + +``` +error[E0506]: cannot assign to `x` because it is borrowed + --> t.rs:4:5 + | +3 | let y = &x; + | -- `x` is borrowed here +4 | x = 3; + | ^^^^^ `x` is assigned to here but it was already borrowed +5 | println!("{x} {y}"); + | --- borrow later used here +``` + +What happens is that creating a (mutable) reference `y` that refers to +`x`, *borrows* the object referred to by the name `x`, so `x` cannot be +used directly anymore until `y` goes out of scope. + +If you want to know more, check out the +[ownership chapter in the book](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html). + +## A tricky example + +Let's say we have a vector of vectors, and we want to copy an element from +one of the internal vectors to another. We could try something like this: + +``` +fn main() { + let mut v = vec![vec![23], vec![42]]; // v is now {{23}, {42}} + v[0].push(v[1][0]); +} +``` + +But this won't compile. Indeed we get: + +``` +error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable + --> a.rs:3:15 + | +3 | v[0].push(v[1][0]); + | - ---- ^ immutable borrow occurs here + | | | + | | mutable borrow later used by call + | mutable borrow occurs here + | + = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices +``` + +However, the following works just fine: + +``` +fn main() { + let mut v = vec![vec![23], vec![42]]; + let x = v[1][0]; + v[0].push(x); +} +``` + +And at this point I was very confused. Whatever the borrow checker does, +shouldn't the two pieces of code do exactly the same? I got stuck for +a while thinking that for some reason the function argument of `push()` +was passed by reference in the first case, while it was copied in the +second, but this is not where the problem lies. + +I was finally able to understand the problem when I realized that my first +piece of code is equivalent to the following, which also does not compile: + +``` +fn main() { + let mut v = vec![vec![23], vec![42]]; + let mut v0 = &mut v[0]; + let x = v[1][0]; + v0.push(x); +} +``` + +Can you see the issue now? + +## Explanation + +Like in C++ and many other languages, the square +bracket operator is a method on the vector object. +More precisely, in Rust it is syntactic sugar for either the +[`index()`](https://doc.rust-lang.org/std/ops/trait.Index.html) or the +[`index_mut()`](https://doc.rust-lang.org/std/ops/trait.IndexMut.html) +functions, depending if mutability is requested in our usage. In our +example, when we call `v[0].push(...)` this will be translated to a call +to `index_mut()`, because `push()` requires a mutable reference; when +we do e.g. `let x = v[1][0]`, the immutable version will be call instead. + +But the details of `[]` are not important for us. The cause of the problem +is that `let mut v0 = &mut v[0]` creates a mutable reference to *part +of* the object `v`. At this point, `v` is borrowed and cannot be used +directly anymore, even if we just want to immutably access some other +parts of it to make a copy. Thus, when we try to do `let x = v[1][0]`, +the borrow checker complains. + +In the first version of my code all of this happens in the same line, +and this makes it confusing, because the order in which the various +statements of that line are executed is very important. + +## Solution + +Solving the problem is easy, I can just use the second version of my code. +Alternatively I could also try to use `split_at_mut()` as suggested +by the compiler, but this seems overkill in this case; good to keep in +mind though. + +But sometimes understanding a problem is more important than finding +a solution. diff --git a/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md b/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md @@ -0,0 +1,106 @@ +# Declarative package management with Alpine Linux + +It has been almost two months since my last update, and it feels a +bit weird because I have done plenty of tinkering that would be a +good fit for this blog! But for one reason or another, I did not +feel like writing about any of this in the usual level of detail. + +However, one of the last things I did is quite simple to explain, so here +is a short post about it. + +## Alpine linux + +A couple of days ago I decided to try out +[Alpine Linux](https://www.alpinelinux.org), a lightweight distro that +uses [musl libc](https://www.musl-libc.org/) instead of GNU libc and +[busybox](https://busybox.net) instead of GNU coreutils. + +Alpine's package manager is called Alpine Package Keeper (APK, not to +be confused with Android Package, which also goes by APK). It is very +fast and simple to use, and it has an especially cool feature: the +list of all manually installed packages is kept in the plain text file +`/etc/apk/world`. This file can be edited by hand, and running `apk fix` +will then add or remove packages to satisfy the list in the file. Neat! + +## Declarative package management + +Unfortunately, APK will also overwrite `/etc/apk/world` by removing +empty lines and sorting the packages in alphabetical order, one per +line. Moreover, there is no option to write comments in this file. +So one cannot really use it as a (commented) list of the packages they +want to keep installed. + +At first I thought I could keep such a list in a separate file, and write +a script to parse this file, remove the comments, write the result to +`/etc/apk/world` and then run `apk fix`. + +But then I thought it would be even better to merge the two, and here +is the result: + +``` +#!/bin/sh + +# Make a backup copy of the previous /etc/apk/world +cp /etc/apk/world /etc/apk/world.backup + +echo " + +# Base system packages (installed by default) +alpine-base busybox-mdev-openrc doas grub-efi openssh openssl + +# Firmware (installed by default, system-dependent) +linux-firmware-i915 linux-firmware-intel linux-firmware-mediatek linux-firmware-other +linux-firmware-rtl_bt linux-firmware-rtl_nic linux-firmware-xe linux-lts + +# Documentation +docs mandoc-apropos + +# Wifi and other hardware control +iwd openresolv pciutils bluez + +# Audio +pulseaudio pulseaudio-bluez pulseaudio-alsa alsa-plugins-pulse pulseaudio-utils pulsemixer + +# Core tools (non-X) +coreutils-fmt curl imagemagick ffmpeg tmux ghostscript ncurses shellcheck +kbd # For console keyboard configuration +syncthing fzf sfeed yt-dlp mblaze msmtp oath-toolkit + +# Development tools +build-base git gdb valgrind clang20 python3 rust cargo hare +lowdown darkhttpd # Both used for updating my website +libx11-dev libxft-dev libxinerama-dev + +# Xorg xorg-server +xinit eudev mesa-dri-gallium xf86-video-intel xf86-input-libinput xf86-input-synaptics +setxkbmap xsel xbanish xsetroot xwallpaper xev slock + +# X applications +firefox libreoffice telegram-desktop vlc imv-x11 +zathura-djvu zathura-pdf-mupdf zathura-ps +arandr + +# Fonts, but like a gazillion of them +font-terminus font-noto font-noto-extra font-arabic-misc +font-misc-cyrillic font-mutt-misc font-screen-cyrillic +font-winitzki-cyrillic font-cronyx-cyrillic font-noto-arabic +font-noto-armenian font-noto-cherokee font-noto-devanagari +font-noto-ethiopic font-noto-georgian font-noto-hebrew font-noto-lao +font-noto-malayalam font-noto-tamil font-noto-thaana font-noto-thai + +" | sed 's/#.*//' | grep -v '^[:space:]*$' > /etc/apk/world +apk fix +``` + +The bulk of the file is a +[here document](https://en.wikipedia.org/wiki/Here_document) with a long +list of all the packages I want installed - Alpine is really minimal! The +few lines of code above and below this list are there to make a backup a +copy of the old `/etc/apk/world`, filter out the comment from the new list +using [sed](../2023-12-03-sed) and [grep](../2023-08-20-grep), +copy the new list to `/etc/apk/world` and finally run `apk fix`. + +Now when I want to add a new piece of software to my system +I edit this file and then run it as root. A dead simple +way to do declarative package management - take that, +[Nix](https://en.wikipedia.org/wiki/Nix_(package_manager))! diff --git a/src/git/git.md b/src/git/git.md @@ -16,16 +16,12 @@ Here are some highlights of what you can find in my git repositories. * [nissy](https://git.tronto.net/nissy): A Rubik's cube solver, intended primarily as a tool for practicing FMC. Since april 2023 - the project has been split into the FMC trainer - [nissy-fmc](https://git.tronto.net/nissy-fmc) and the optimal solver - [nissy-nx](https://git.tronto.net/nissy-nx). Both are work in - progress. The stable version is available at - [nissy-classic](https://git.tronto.net/nissy-classic), and is - maintained with bugfixes. + the stable version of the project has been moved to + [nissy-classic](https://git.tronto.net/nissy-classic), while I kept + working on the H48 optimal solver. This new solver, which will be + integrated in a future version of nissy, can be found + [here](https://git.tronto.net/nissy-core). Check out also [the project's homepage](https://nissy.tronto.net). -* [h48](https://git.tronto.net/h48): Another Rubik's cube optimal solver, - intended to replace the engine of nissy in the future. Written in - collaboration with [Enrico](https://github.com/enricotenuti). * [scripts](https://git.tronto.net/scripts): Various scripts for Unix-like systems. * [zmodn](https://git.tronto.net/zmodn): A simple C++ library for working diff --git a/src/research/research.md b/src/research/research.md @@ -33,11 +33,11 @@ for a list of slides and notes of my talks. * Tronto, Sebastiano. *Division in modules and Kummer theory.* - [arXiv preprint](https://arxiv.org/abs/2111.14363). + [Journal de Thórie des Nombres de Bordeaux (2025) - open access](https://jtnb.centre-mersenne.org/articles/10.5802/jtnb.1326) * Lombardo, Davide; Tronto, Sebastiano. *Some uniform bounds for elliptic curves over Q.* - [Pacific Journal of Mathematics, (2022)](https://msp.org/pjm/2022/320-1/p06.xhtml). + [Pacific Journal of Mathematics (2022)](https://msp.org/pjm/2022/320-1/p06.xhtml). * Lombardo, Davide; Tronto, Sebastiano. *Effective Kummer theory for elliptic curves.* diff --git a/src/series/series.md b/src/series/series.md @@ -50,3 +50,13 @@ My adventures in learning C++ as a C programmer. * Episode 1: [motivation](../blog/2024-04-30-taming-cpp-motivation) * Episode 2: [RAII](../blog/2024-12-26-taming-cpp-raii) * Episode 3: [templates, constraints and concepts](../blog/2025-01-21-taming-cpp-templates) + +## Learning Rust + +I have recently started learning Rust, documenting the process in a +small series of blog posts. These posts are all marked by the crab emoji +'🦀', which is something of a meme in the Rust community, as far as +I understand. + +* [Cargo culture shock](../blog/2025-06-13-cargo-culture-shock) +* [Stunned by the borrow checker](../blog/2025-06-26-borrow-checker) diff --git a/src/speedcubing/bldsheets/corners.csv b/src/speedcubing/bldsheets/corners.csv @@ -1,22 +1,22 @@ "","UBR","UBL","UFL","DFR","DFL","DBL","DBR","RUB","RDF","RDB","LUF","LUB","LDB","LDF","FUL","FDL","FDR","BUL","BUR","BDR","BDL", -"UBR","","[R' B' R: U', R D R']","[R F R': R' D' R, U]","[R' D R U' R D' R', U]","[R F': R' U' R, D]","[R' B: D', R U R']","[U', R D' R' U R' D R]","","[R' D' R, U]","[U', R D R']","[R': R' D' R, U']","[R: U, R D R']","[U', R D' R']","[R' D R, U]","[R: U2, R D R']","[D R' D' R, U]","[D' R' D R, U]","[R': R' D' R, U2]","","[U', D R D' R']","[U': D' R D R']", -"UBL","[R' B' R: R D R', U']","","[R' D' R U': U', R' D R]","[U R' D R: U2, R D' R']","[R F' R' U': R D R', U2]","[R' D' R: R U2 R', D]","[R D' R': R' D R, U2]","[U R D' U: R' D' R, U2]","[R' D' R, U2]","[U': R D R', U2]","[F: U2, R' D' R]","","[U': R D' R', U2]","[R' D R, U2]","[R' F R D': R D' R', U] go back to this","[D R' D' R, U2]","[D' R' D R, U2]","","[R': U2, D' R' D R]","[D': R' D' R, U2] but maybe switch","[D: R' D R, U2] but maybe switch to (see second row)", -"UFL","[R F R': U, R' D' R]","[R' D' R U': R' D R, U']","","[R' D R U' R D' R', U']","[U': U R' U' R, F2] sledge","[R' D' R: R U' R', D]","[R D' R': R' D R, U']","[U' R': U2, R' D' R]","[R' D' R, U']","[U': R D R', U']","","[U' R: R D R', U2]","[U': R D' R', U']","[R' D R, U']","","[D: R' D' R, U']","[D' R' D R, U']","[U' R': U', R' D' R]","[R': U', D' R' D R]","[D': R' D' R, U']","[D: R' D R, U']", -"DFR","[U, R' D R U' R D' R']","[U R' D R: R D' R', U2]","[U': R' D R U' R D' R']","","[U' R' U R: R U' R', D]","[R U' R' U: R D' R', U2]","[R U' R' D R' U R, D']","[U' D' R': U2, R' D R]","","[D' R: F2, R' U R U'] sledge","[R U' R': U2, R' D' R]","[U2 R': R' U R, D] or U R' D'...","[U2 R': R' U R, D']","[U D R: D' R' D R, U']","[R U' R': U2, D' R' D R]","[R U' R': U2, R' D R]","","[U' D' R': U', R' D R]","[U R': D', U' R U R']","[D' R: D R' D' R, U]","[U' D' R': U, R' D R]", -"DFL","[R F': D, R' U' R]","[R F' R' U: R D R', U2]","[U': F2, U R' U' R] sledge","[U' R' U R: D, R' U' R]","","[D' R U' R': D', R' U R]","[R U' R': D2, R' U R] or nightmare? (see below)","[U' R': U2, R' D R]","[R: D R' D' R, U]","[U' R': U, R' D R]","[U' R': D, U R U' R']","[D' R: U, R D' R']","[D R U' R': U2, R' D R]","","[R U' D: R' D' R, U2]","","[R: F2, R' U R U'] sledge","[U' R': U', R' D R]","[U D R' U': D', R U R'] or D' R D'...","[D' R: U', R D' R']","[U' D' R': U, R' D2 R]", -"DBL","[R' B: R U R', D']","[R' D' R: D, R U2 R']","[R' D' R: D, R U' R']","[R U' R' U': R D' R', U2]","[D' R U' R': R' U R, D']","","[R U' R': D', R' U R]","[U' D R': U2, R' D2 R]","[R: U', R D' R']","[U' R': U, R' D2 R]","[F R: R D' R', U']","[R: U, R D' R']","","[D R: F2, R' U R U'] sledge","[R: U2, R D' R']","[D R: D R' D' R, U]","[R' D' R: D, R U R'] new!","[U' D R': U', R' D R]","[R: D', U' R' U R]","[R D': U' R' U R, D2]","", -"DBR","[R D' R' U R' D R, U']","[R D' R': U2, R' D R]","[R D' R': U', R' D R]","[D': R U' R' D R' U R]","[R U' R': R' U R, D2] or nightmare? see NM","[R U' R': R' U R, D']","","[R': U, R' D R U' R D' R']","[R: R' D R U' R D' R', U]","","[R U' R': R' U R, D]","[D R: U, R D' R']","[R D' R': U, R' D R]","[U R: R U' R', D]","[D R: U2, R D' R']","[D R: U', R D' R']","[U R: D' R' D R, U']","[U R: R U' R', D']","[D R: U' R' U R, D']","","[U R: R U' R', D2]", -"RUB","","[U R U' D': R' D' R, U2]","[U' R': R' D' R, U2]","[U' D' R': R' D R, U2]","[U' R': R' D R, U2]","[U' D R': R' D R, U2]","[R': R' D R U' R D' R', U]","","[R: U', R D' R' U R' D R]","[U' D R' U': R D' R', U2] new!","[R U' D' R': R' U R, D] new!","[R: U, R D' R' U R' D R]","[U R: U2, R D' R']","[U R U': R' D R, U2]","[R': U', R' D R U' R D' R']","[U D' R: U2, R D' R']","[U' R' U': R D R', U2]","[U R: U2, R D R']","","[U D R: U2, R D' R']","[U' R' U': R D' R', U2]", -"RDF","[U, R' D' R]","[U2, R D' R]","[U', R' D' R]","","[R: U, D R' D' R]","[R: R D' R', U']","[R: U, R' D R U' R D' R']","[R: R D' R' U R' D R, U']","","[R: U, D' R' D R]","[R', U' R U R', D']","[U R' U': R D R', U']","[R U R', D2]","[U R' D': R U' R', D2]","[D' R U': R' D R, U2]","[R U R', D]","","[R': D, R' U R]","[R': U R U' R', D']","[R U R', D']","[U R' D': R U' R', D']", -"RDB","[R D R', U']","[U': R D R', U2]","[U': U', R D R']","[D' R: R' U R U', F2] sledge","[U' R': R' D R, U]","[U' R': R' D2 R, U]","","[U' D R' U: R D' R', U2] new!","[R D': R' D R, U]","","[R' U': R' D' R, U2]","[U' R: D', R U' R']","[R D: R' U' R, D2]","[D': U R U' R', D2]","[R U': D' R' D R, U2]","[R D: R' U' R, D]","[D', U R U' R']","[R' U: R' D' R, U]","[U' R: U R' U' R, D]","","[U' R' U' R, D']", -"LUF","[R': U', R' D' R]","[F: R' D' R, U2]","","[R U' R': R' D' R, U2]","[U' R': U R U' R', D]","[F R: U', R D' R']","[R U' R': D, R' U R]","[R U' D' R': D, R' U R] new!","[R': D', U' R U R']","[R': R' D' R, U2]","","[U' R' U, L]","[F: D2, R U R'] or RUD see below","[R': D, U' R U R']","","[D R': D', U' R U R']","[R: F, R' U R U']","[R' U': R' D' R, U']","[R' F: R U R', D]","[F: D', R U R']","[D R': D, U' R U R']", -"LUB","[R: R D R', U]","","[U' R: U2, R D R']","[U2 R': D, R' U R] or U R' D'...","[D' R: R D' R', U]","[R: R D' R', U]","[D R: R D' R', U]","[R: R D' R' U R' D R, U]","[U R' U': U', R D R']","[U' R: R U' R', D']","[L, U' R' U]","","[R: D' R D R', U]","[U R': U', R' D R]","[R' B' R2 U': R' D' R, U2]","[R U: R' D R, U]","[R U R': R' D' R, U']","","[R', F' L F]","[R U: R' D' R, U]","[U D R': U', R' D R]", -"LDB","[R D' R', U']","[U: R D' R', U2]","[U': U', R D' R']","[U2 R': D', R' U R]","[D R U' R': R' D R, U2]","","[R D' R': R' D R, U]","[U R: R D' R', U2]","[D2, R U R']","[R D': R' U' R, D2]","[F: R U R', D2] or RUD see jw","[R: U, D' R D R']","","[D R': R D' R' D, F']","[D R U': R' D R, U2]","[D: D, R U R']","[U R U': R D' R', U']","[U2: R: R D' R', U]","[U' R: U R' U' R, D']","[D': D', R U R']","", -"LDF","[U, R' D R]","[U2, R' D R]","[U', R' D R]","[U D R: U', D' R' D R]","","[D R: R' U R U', F2] sledge","[U R: D, R U' R']","[U R U: R' D R, U2]","[U R' D: R U' R', D2]","[D: U R U' R', D2]","[R': U' R U R', D]","[U R': R' D R, U']","[D R': F', R D' R' D] sledge","","[R' D: D' R D R', F2] sledge","","[D, U R U' R']","[U' R': U', D R' D' R]","[R': U R U' R', D]","[R' D: R' U' R, D]","[D: U R U' R', D]", -"FUL","[R: R D R', U2]","[R' F R D': U, R D' R'] go back to this","","[R U R': D' R' D R, U2]","[R U D: R' D' R, U2]","[R: R D' R', U2]","[D R: R D' R', U2]","[R': R' D R U' R D' R', U']","[D R U: R' D' R, U2]","[R U: D' R' D R, U2]","","[R' B' R2 U: R' D' R, U2]","[D R U: R' D R, U2] or R U D (see below)","[D R': R D' R' D, F2] sledge","","[R U: R' D R, U2]","[R': R D' R' D, F2] sledge","[R U R' D': U2, R' D R]","[R D' U: R' D' R, U2]","[R U: R' D' R, U2]","[D' R U: D' R' D R, U2]", -"FDL","[U, D R' D' R]","[U2, D R' D' R]","[U', D R' D' R]","[R U' R': R' D R, U2]","","[D R: U, D R' D' R]","[D R: U', R D' R']","[U D' R: R D' R', U2]","[D, R U R']","[R D: R' U' R, D]","[D R': U R U' R', D']","[R U: U, R' D R]","[D: R U R', D]","","[R U': R' D R, U2]","","[R': R D' R' D, F']","[D R': D, R' U R]","[R' F: R U' R', D] different from Elliot","[D: R U R', D2]","[R' F': R U R', D2] or D' R D'...", -"FDR","[U, D' R' D R]","[U2, D' R' D R]","[U', D' R' D R]","","[R: R' U R U', F2] sledge","[R' D' R: R U R', D] new!","[U R D': U', R' D R]","[U' R' U: R D R', U2]","","[U R U' R', D']","[R: R' U R U', F] sledge","[R U R': U', R' D' R]","[U R U': U', R D' R']","[U R U' R', D]","[R': F2, R D' R' D] sledge","[R': F', R D' R' D] sledge","","[U' R' U': R' D R', U]","[D' R': U R U' R', D]","[R U R' U': D', R U R'] or D R D...","[U R U' R', D2]", -"BUL","[R': U2, R' D' R]","","[U' R': R' D' R, U']","[U R' U': R U R', D']","[U' R': U', R' D R]","[U' D R': R' D R, U']","[U R: D', R U' R']","[U R: R D R', U2]","[R': R' U R, D]","[R' U: U, R' D' R]","[R' U': U', R' D' R]","","[U2 R: U, R D' R']","[U' R': D R' D' R, U']","[R U R' D': R' D R, U2]","[D R': R' U R, D]","[U' R' U': R D R', U']","","[U' L' U, R]","[D' R': R' U R, D]","[U' R' U': R D' R', U']", -"BUR","","[R': D' R' D R, U2]","[R': D' R' D R, U']","[U R': U' R U R', D']","[U D R' U': R U R', D'] or D' R U'...","[R: U' R' U R, D']","[D R: U' R' U R, D']","","[R': D', U R U' R']","[U' R: D, U R' U' R]","[R' F: D, R U R']","[F' L F, R']","[U' R: D', U R' U' R]","[R': D, U R U' R']","[R D' U': R' D' R, U2]","[R' F: D, R U' R'] different from Elliot","[D' R': D, U R U' R']","[R, U' L' U]","","[R': D' R' D R, U]","[D R': D, U R U' R']", -"BDR","[D R D' R', U']","[D': R' D' R, U2] but maybe switch to D U'...","[D': U', R' D' R]","[D' R: U, D R' D' R]","[D' R: R D' R', U']","[R D: U' R' U R, D2]","","[U D R: R D' R', U2]","[D', R U R']","","[F: R U R', D']","[R U: U, R' D' R]","[D': R U R', D']","[R' D: D, R' U' R]","[R U': R' D' R, U2]","[D': R U R', D2]","[R U R' U': R U R', D'] or D R D'...","[D' R': D, R' U R]","[R': U, D' R' D R]","","[R' D': R' U' R, D2] or D' R...", -"BDL","[D' R D R', U']","[D: U2, R' D R] but maybe switch to D' U'","[D: U', R' D R]","[U' D' R': R' D R, U]","[U' D' R': R' D2 R, U]","","[U R: D2, R U' R']","[U' R' U: R D' R', U2]","[U R' D': D', R U' R']","[U' R' U' R, D']","[D R': U R U R', D]","[U D R': R' D R, U']","","[D: D, U R U' R']","[D' R U': D' R' D R, U2]","[R' F': D2, R U R'] or D' R D...","[D2, U R U' R']","[U' R' U': U', R D' R']","[D R': U R U' R', D]","[R' D: R' U' R, D2] or D' R...","", +"UBR","","[R' B' R: U', R D R']","[R F R': R' D' R, U]","[R' D R U' R D' R', U]","[R F': R' U' R, D]","[R' B: D', R U R']","[U', R D' R' U R' D R]","","[R' D' R, U]","[U', R D R']","[R': R' D' R, U']","[R: U, R D R']","[U', R D' R']","[R' D R, U]","[R: U2, R D R']","[D R' D' R, U]","[D' R' D R, U]","[R': R' D' R, U2]","","[U', D R D' R']","[U', D' R D R']", +"UBL","[R' B' R: R D R', U']","","[R' D' R U': U', R' D R]","[U R' D R: U2, R D' R']","[R F' R' U': R D R', U2]","[R' D' R: R U2 R', D]","[R D' R': R' D R, U2]","[U R D' U: R' D' R, U2]","[R' D' R, U2]","[U': R D R', U2]","[F: U2, R' D' R]","","[U': R D' R', U2]","[R' D R, U2]","[R' F R D': U, R D' R']","[D R' D' R, U2]","[D' R' D R, U2]","","[R': U2, D' R' D R]","[D U': R D' R', U2]","[D' U': R D R', U2]", +"UFL","[R F R': U, R' D' R]","[R' D' R U': R' D R, U']","","[R' D R U' R D' R', U']","[U' R F': D, R' U' R]","[R' D' R: R U' R', D]","[R D' R': R' D R, U']","[U' R': U2, R' D' R]","[R' D' R, U']","[U': R D R', U']","","[U' R: R D R', U2]","[U': R D' R', U']","[R' D R, U']","","[D: R' D' R, U']","[D' R' D R, U']","[U' R': U', R' D' R]","[R': U', D' R' D R]","[D': R' D' R, U']","[D: R' D R, U']", +"DFR","[U, R' D R U' R D' R']","[U R' D R: R D' R', U2]","[U', R' D R U' R D' R']","","[U' R' U R: R U' R', D]","[R U' R' U: R D' R', U2]","[R U' R' D R' U R, D']","[U' D' R': U2, R' D R]","","[D' R: F2, R' U R U']","[R U' R': U2, R' D' R]","[U2 R': R' U R, D]","[U2 R': R' U R, D']","[U D R: D' R' D R, U']","[R U' R': U2, D' R' D R]","[R U' R': U2, R' D R]","","[U D R: R U' R', D']","[U R': D', U' R U R']","[D' R: D R' D' R, U]","[U' D' R': U, R' D R]", +"DFL","[R F': D, R' U' R]","[R F' R' U: R D R', U2]","[U' R F': R' U' R, D]","[U' R' U R: D, R U' R']","","[D' R U' R': D', R' U R]","[R U' R': D2, R' U R]","[U' R': U2, R' D R]","[R: D R' D' R, U]","[U' R': U, R' D R]","[U' R': D, U R U' R']","[D' R: U, R D' R']","[D R U' R': U2, R' D R]","","[R U' D: R' D' R, U2]","","[R: F2, R' U R U']","[U' R': U', R' D R]","[U D R' U': D', R U R']","[D' R: U', R D' R']","[U' D' R': U, R' D2 R]", +"DBL","[R' B: R U R', D']","[R' D' R: D, R U2 R']","[R' D' R: D, R U' R']","[R U' R' U': R D' R', U2]","[D' R U' R': R' U R, D']","","[R U' R': D', R' U R]","[U' D R': U2, R' D R]","[R: U', R D' R']","[U' R': U, R' D2 R]","[F R: R D' R', U']","[R: U, R D' R']","","[D R: F2, R' U R U']","[R: U2, R D' R']","[D R: D R' D' R, U]","[R' D' R: D, R U R']","[U' D R': U', R' D R]","[R: D', U' R' U R]","[R D': U' R' U R, D2]","", +"DBR","[R D' R' U R' D R, U']","[R D' R': U2, R' D R]","[R D' R': U', R' D R]","[D', R U' R' D R' U R]","[R U' R': R' U R, D2]","[R U' R': R' U R, D']","","[R': U, R' D R U' R D' R']","[R: R' D R U' R D' R', U]","","[R U' R': R' U R, D]","[D R: U, R D' R']","[R D' R': U, R' D R]","[U R: R U' R', D]","[D R: U2, R D' R']","[D R: U', R D' R']","[U R: D' R' D R, U']","[U R: R U' R', D']","[D R: D', U' R' U R]","","[U R: R U' R', D2]", +"RUB","","[U R U' D': R' D' R, U2]","[U' R': R' D' R, U2]","[U' D' R': R' D R, U2]","[U' R': R' D R, U2]","[U' D R': R' D R, U2]","[R': R' D R U' R D' R', U]","","[R: U', R D' R' U R' D R]","[U R U': R' D' R, U2]","[R U' D' R': R' U R, D]","[R: U, R D' R' U R' D R]","[U R: U2, R D' R']","[U R U': R' D R, U2]","[R': R' D R U' R D' R', U']","[U D' R: U2, R D' R']","[U' R' U': R D R', U2]","[U R: U2, R D R']","","[U D R: U2, R D' R']","[U' R' U': R D' R', U2]", +"RDF","[U, R' D' R]","[U2, R' D' R]","[U', R' D' R]","","[R: U, D R' D' R]","[R: R D' R', U']","[R: U, R' D R U' R D' R']","[R: R D' R' U R' D R, U']","","[R: U, D' R' D R]","[R': U' R U R', D']","[U R' U': R D R', U']","[R U R', D2]","[U R' D': R U' R', D2]","[D' R U': R' D R, U2]","[R U R', D]","","[R': D, R' U R]","[R': U R U' R', D']","[R U R', D']","[U R' D': R U' R', D']", +"RDB","[R D R', U']","[U: R D R', U2]","[U': U', R D R']","[D' R: R' U R U', F2]","[U' R': R' D R, U]","[U' R': R' D2 R, U]","","[U R U: R' D' R, U2]","[R D': R' D R, U]","","[R' U: R' D' R, U2]","[U' R: D', R U' R']","[R D: R' U' R, D2]","[D': U R U' R', D2]","[R U': D' R' D R, U2]","[R D: R' U' R, D]","[D', U R U' R']","[R' U: R' D' R, U]","[U' R: U R' U' R, D]","","[U' R' U' R, D']", +"LUF","[R': U', R' D' R]","[F: R' D' R, U2]","","[R U' R': R' D' R, U2]","[U' R': U R U' R', D]","[F R: U', R D' R']","[R U' R': D, R' U R]","[R U' D' R': D, R' U R]","[R': D', U' R U R']","[R' U': R' D' R, U2]","","[U' R' U, L]","[F: D2, R U R']","[R': D, U' R U R']","","[D R': D', U' R U R']","[R: F, R' U R U']","[R' U': R' D' R, U']","[R' F: R U R', D]","[F: D', R U R']","[D R': D, U' R U R']", +"LUB","[R: R D R', U]","","[U' R: U2, R D R']","[U2 R': D, R' U R]","[D' R: R D' R', U]","[R: R D' R', U]","[D R: R D' R', U]","[R: R D' R' U R' D R, U]","[U R' U': U', R D R']","[U' R: R U' R', D']","[L, U' R' U]","","[R: D' R D R', U]","[U R': U', R' D R]","[R' B' R2 U': R' D' R, U2]","[R U: R' D R, U]","[R U R': R' D' R, U']","","[R', F' L F]","[R U: R' D' R, U]","[U D R': U', R' D R]", +"LDB","[R D' R', U']","[U: R D' R', U2]","[U': U', R D' R']","[U2 R': D', R' U R]","[D R U' R': R' D R, U2]","","[R D' R': R' D R, U]","[U R: R D' R', U2]","[D2, R U R']","[R D': R' U' R, D2]","[F: R U R', D2]","[R: U, D' R D R']","","[D R': R D' R' D, F']","[D R U': R' D R, U2]","[D: D, R U R']","[U R U': R D' R', U']","[U2 R: R D' R', U]","[U' R: U R' U' R, D']","[D': D', R U R']","", +"LDF","[U, R' D R]","[U2, R' D R]","[U', R' D R]","[U D R: U', D' R' D R]","","[D R: R' U R U', F2]","[U R: D, R U' R']","[U R U: R' D R, U2]","[U R' D: R U' R', D2]","[D: U R U' R', D2]","[R': U' R U R', D]","[U R': R' D R, U']","[D R': F', R D' R' D]","","[R' D: D' R D R', F2]","","[D, U R U' R']","[U' R': U', D R' D' R]","[R': U R U' R', D]","[R' D: R' U' R, D]","[D: U R U' R', D]", +"FUL","[R: R D R', U2]","[R' F R D': R D' R', U]","","[R U' R': D' R' D R, U2]","[R U D: R' D' R, U2]","[R: R D' R', U2]","[D R: R D' R', U2]","[R': U', R' D R U' R D' R']","[D R U: R' D' R, U2]","[R U: D' R' D R, U2]","","[R' B' R2 U: R' D' R, U2]","[D R U: R' D R, U2]","[D R': R D' R' D, F2]","","[R U: R' D R, U2]","[R': R D' R' D, F2]","[R U R' D': U2, R' D R]","[R D' U: R' D' R, U2]","[R U: R' D' R, U2]","[D' R U: D' R' D R, U2]", +"FDL","[U, D R' D' R]","[U2, D R' D' R]","[U', D R' D' R]","[R U' R': R' D R, U2]","","[D R: U, D R' D' R]","[D R: R D' R', U']","[U D' R: R D' R', U2]","[D, R U R']","[R D: D, R' U' R]","[D R': U' R U R', D']","[R U: U, R' D R]","[D: R U R', D]","","[R U': R' D R, U2]","","[R': R D' R' D, F']","[U R' F': D, R U' R']","[R' F: R U' R', D]","[D: R U R', D2]","[R' F': R U R', D2]", +"FDR","[U, D' R' D R]","[U2, D' R' D R]","[U', D' R' D R]","","[R: R' U R U', F2]","[R' D' R: R U R', D]","[U R D': U', R' D R]","[U' R' U: R D R', U2]","","[U R U' R', D']","[R: R' U R U', F] sledge","[R U R': U', R' D' R]","[U R U': U', R D' R']","[U R U' R', D]","[R': F2, R D' R' D]","[R': F', R D' R' D]","","[U' R' U': U', R D R']","[D' R': U R U' R', D]","[R U R' U': D', R U R']","[U R U' R', D2]", +"BUL","[R': U2, R' D' R]","","[U' R': R' D' R, U']","[U D R: D', R U' R']","[U' R': R' D R, U']","[U' D R': R' D R, U']","[U R: D', R U' R']","[U R: R D R', U2]","[R': R' U R, D]","[R' U: U, R' D' R]","[R' U': U', R' D' R]","","[U2 R: U, R D' R']","[U' R': D R' D' R, U']","[R U R' D': R' D R, U2]","[U R' F': R U' R', D]","[U' R' U': R D R', U']","","[U' L' U, R]","[D' R': R' U R, D]","[U' R' U': R D' R', U']", +"BUR","","[R': D' R' D R, U2]","[R': D' R' D R, U']","[U R': U' R U R', D']","[U D R' U': R U R', D']","[R: U' R' U R, D']","[D R: U' R' U R, D']","","[R': D', U R U' R']","[U' R: D, U R' U' R]","[R' F: D, R U R']","[F' L F, R']","[U' R: D', U R' U' R]","[R': D, U R U' R']","[R D' U': R' D' R, U2]","[R' F: D, R U' R']","[D' R': D, U R U' R']","[R, U' L' U]","","[R': D' R' D R, U]","[D R': D, U R U' R']", +"BDR","[D R D' R', U']","[D U: R D' R', U2]","[D': U', R' D' R]","[D' R: U, D R' D' R]","[D' R: R D' R', U']","[R D: U' R' U R, D2]","","[U D R: R D' R', U2]","[D', R U R']","","[F: R U R', D']","[R U: U, R' D' R]","[D': R U R', D']","[R' D: D, R' U' R]","[R U': R' D' R, U2]","[D': R U R', D2]","[R U R' U': R U R', D']","[D' R': D, R' U R]","[R': U, D' R' D R]","","[R' D': R' U' R, D2]", +"BDL","[D' R D R', U']","[D' U: R D R', U2]","[D: U', R' D R]","[U' D' R': R' D R, U]","[U' D' R': R' D2 R, U]","","[U R: D2, R U' R']","[U' R' U: R D' R', U2]","[U R' D': D', R U' R']","[D', U' R' U' R]","[D R': U' R U R', D]","[U D R': R' D R, U']","","[D: D, U R U' R']","[D' R U': D' R' D R, U2]","[R' F': D2, R U R']","[D2, U R U' R']","[U' R' U': U', R D' R']","[D R': U R U' R', D]","[R' D: R' U' R, D2]","", diff --git a/src/speedcubing/bldsheets/edges.csv b/src/speedcubing/bldsheets/edges.csv @@ -1,23 +1,23 @@ "","UB","UL","UF","DR","DF","DL","DB","RB","RF","RD","LF","LU","LB","LD","FR","FU","FL","FD","BL","BU","BR","BD", -"UB","","[M2 U: M', U2]","[R' B' R: U', R E R']","[R U' R': S, R2]","[R U' R' D: S, R2]","[S R B' R': S, R2]","[U', M' U2 M']","[U': U', R' S' R]","[U': U', R S' R']","[R E R', U]","[R' E R, U]","[S, U R' U' R]","[R E' R', U]","[U': U', R' S R]","[E': R' E R, U]","[R U R' U', M']","[R E R2 E' R, U]","[U: M', R U' R' U]","[R' E' R2 E R', U]","","[E: R E' R', U]","[R' U R U', M]", -"UL","[M2 U': M', U2]","","[M2 U: M, U2]","[S, R2]","[U': M', U2]","[L2, S']","[U: M, U2]","[U2, R' S' R]","[U2, R S' R']","[R E R', U2]","[R' E R, U2]","","[R E' R', U2]","[U2, R' S R]","[R, S' l2 S]","[M U': M', U2]","[S R2 S', l']","[M U: M, U2]","[S R2 S', l]","[M' U: M, U2]","[R', S' l2 S]","[M' U': M', U2]", -"UF","[R' B' R: R E R', U']","[M2 U': M, U2]","","[R U R': S, R2]","[U, M U2 M]","[S R' F R: S, R2]","[R U R' D': R2, S]","[U: U, R' S' R]","[U: U, R S' R']","[R' E' R, U']","[R' E R, U']","[S, U' R' U R]","[R E' R', U']","[U: U, R' S R]","[E': R' E R, U']","","[R E R2 E' R, U']","[R U' R' U, M']","[R' E' R2 E R', U']","[R' U' R U, M]","[E: R E' R', U']","M U' M' U' M U' M' U'", -"DR","[R' U' R': S, R2]","[R2, S]","[R' U R': S, R2]","","[R D' R': S, R2]","[S', R2]","[R D R': S, R2]","[R' S' R': S, R2]","[R S' R: S, R2]","","[S' l' S, R2]","[l': S' l S, R2]","R2 S R' E' R2 E R S'","[R: E l E', R2]","[R U2 R: S, R2]","[r U' R: S, R2]","[R: E, R2]","[S' U: M', U2]","[R': E', R2]","[r' U R': S, R2]","[R' U2 R': S, R2]","[S' U: M, U2]", -"DF","[R U' R' D: R2, S]","[U: M', U2]","[M U2 M, U]","[R D' R: S', R2]","","[S R' F' R: S, R2]","[D': R2, S']","[R' S' R U: M', U2]","[R S' R' U: M', U2]","[R F' R', S]","[R F R': R' E R, U']","[U2: R' F' R, S]","[S R' F': R2, E']","[R' S R U: M', U2]","[R, U' M2 U]","[R' F': R U' R', E]","[D R: E, R2]","","[D R': E', R2]","[r' U' r U': M', U2]","[R', U' M2 U]","[r' U' r U: M', U2]", -"DL","[S' R B' R': S, R2]","[S', L2]","[S' R' F R: S, R2]","[R2, S']","[S' R' F' R: S, R2]","","[S' R B R': S, R2]","[R' S' R: R S' R', U2]","[R S' R': R' S' R, U2]","[U: S', R F R']","[S U2 R': E, R2]","[R' F' R: R' S R, U]","[S U2 R: E', R2]","","[U R' U': R2, S']","[R F R: S', R2]","[l': l', S R2 S']","[R F' R: S', R2]","[l: l, S R2 S']","[R' B' R': S', R2]","[U R U': R2, S']","[R' B R': S, R2]", -"DB","[M' U2 M', U']","[U': M, U2]","[R U R' D': S, R2]","[R D R: S, R2]","[D': R2, S']","[S R B R': S, R2]","","[R' S' R U': M, U2]","[R S' R' U': M, U2]","[R' B R, S]","[S R B: R2, E]","[U2: R B R', S]","[R' B' R: U, R E' R']","[R' S R U': M, U2]","[R, U M2 U']","[r U' r' U': M, U2]","[D' R: E, R2]","[r U r' U': M, U2]","[D' R': E', R2]","[U D': S', R' F R]","[R', U M2 U']","", -"RB","[U': R' S' R, U']","[R' S' R, U2]","[U: R' S' R, U]","[R' S' R: S, R2]","[R' S' R U': M', U2]","[R' S' R: U2, R S' R']","[R' S' R U: M, U2]","","[S R: S, R2]","[U' R U, S]","[R' S': E, R U2 R']","[R' U2 R, S']","[R' S' R: R E' R', U2]","[R' S': R U2 R', S2]","[u: R U' R', E]","[R' U: U, R' S' R]","[S' R': U2, R' E R]","[S' R' U: M', U2]","[E, R B2 R']","[R' U': U', R S' R']","","[S' R' U': M, U2]", -"RF","[U': R S' R', U']","[R S' R', U2]","[U: R S' R', U]","[R S' R': S, R2]","[R S' R' U': M', U2]","[R S' R': U2, R' S' R]","[R S' R' U: M, U2]","[S R': S, R2]","","[U' R' U, S]","[R S' R': R' E R, U2]","[R U2 R', S']","[R S': E', R' U2 R]","[R S': R' U2 R, S2]","","[R U: U, R' S' R]","[E': R' F2 R]","[S' R U: M', U2]","[S' R: U2, R E' R']","[R U': U', R' S' R]","[u': R' U R, E']","[S' R U': M, U2]", +"UB","","[R' E R U: R' E' R, U]","[R' E R U: R' E' R, U2]","[R E' R2 E R, U]","[R U' R' D: S, R2]","[S R B' R': S, R2]","[U', M' U2 M']","[U': U', R' S' R]","[U': U', R S' R']","[R E R', U]","[R' E R, U]","[S: U, R E R']","[R E' R', U]","[U': U', R' S R]","[U': U', R' S2 R]","[R U R' U', M']","[R E2 R', U]","[U: M', R U' R' U]","[R' E2 R, U]","","[U': U', R S2 R']","[R' U R U', M]", +"UL","[R' E R U: U, R' E' R]","","[R' E R U': U', R' E' R]","[S, R2]","[U': M', U2]","[L2, S']","[U: M, U2]","[U2, R' S' R]","[U2, R S' R']","[R E R', U2]","[R' E R, U2]","","[R E' R', U2]","[U2, R' S R]","[U2, R' S2 R]","[M U': M', U2]","[R E2 R', U2]","[M U: M, U2]","[R' E2 R, U2]","[M' U: M, U2]","[U2, R S2 R']","[M' U': M', U2]", +"UF","[R' E R U': R' E' R, U2]","[R' E R U': R' E' R, U']","","[R E' R2 E R, U']","[U, M U2 M]","[S R' F R: S, R2]","[R U R' U: M, U2]","[U: U, R' S' R]","[U: U, R S' R']","[R' E' R, U']","[R' E R, U']","[S: U', R E R']","[R E' R', U']","[U: U, R' S R]","[U: U, R' S2 R]","","[R E2 R', U']","[R U' R' U, M']","[R' E2 R, U']","[R' U' R U, M]","[U: U, R S2 R']","M U' M' U' M U' M' U'", +"DR","[U, R E' R2 E R]","[R2, S]","[U', R E' R2 E R]","","[R D' R': S', R2]","[S', R2]","[R D R': S', R2]","[R' S' R': S, R2]","[R S' R: S, R2]","","[S' l' S, R2]","[l': S' l S, R2]","R2 S R' E' R2 E R S'","[R: E l E', R2]","[R U2 R: S, R2]","[r U' R: S, R2]","[R: E, R2]","[S' U: M', U2]","[R': E', R2]","[r' U R': S, R2]","[R' U2 R': S, R2]","[S' U': M, U2]", +"DF","[R U' R' D: R2, S]","[U: M', U2]","[M U2 M, U]","[R D' R: S', R2]","","[S R' F' R: S, R2]","[D': R2, S']","[R' S' R U: M', U2]","[R S' R' U: M', U2]","[R F' R', S]","[R F R': U', R' E R]","[U2: R' F' R, S]","[S R' F': R2, E']","[R' S R U: M', U2]","[R, U' M2 U]","[R' F': R U' R', E]","[D R: E, R2]","","[D R': E', R2]","[r' U r U: M', U2]","[R', U' M2 U]","[r' U' r U: M', U2]", +"DL","[S' R B' R': S, R2]","[S', L2]","[S' R' F R: S, R2]","[R2, S']","[S' R' F' R: S, R2]","","[S' R B R': S, R2]","[R' S' R: R S' R', U2]","[R S' R': R' S' R, U2]","[U: S', R F R']","[S U2 R': E, R2]","[R' F' R: R' S R, U]","[S U2 R: E', R2]","","[U R' U': R2, S']","[R F R: S', R2]","[l': l', S R2 S']","[R F' R: S', R2]","[l: l, S R2 S']","[R' B' R': S', R2]","[U R U': R2, S']","[R' B R': S', R2]", +"DB","[M' U2 M', U']","[U': M, U2]","[R U R' U': M, U2]","[R D R: S', R2]","[D: R2, S']","[S R B R': S, R2]","","[R' S' R U': M, U2]","[R S' R' U': M, U2]","[R' B R, S]","[S R B: R2, E]","[U2: R B R', S]","[R' B' R: U, R E' R']","[R' S R U': M, U2]","[R, U M2 U']","[r U' r' U': M, U2]","[D' R: E, R2]","[r U r' U': M, U2]","[D' R': E', R2]","[U D': S', R' F R]","[R', U M2 U']","", +"RB","[U': R' S' R, U']","[R' S' R, U2]","[U: R' S' R, U]","[R' S' R: S, R2]","[R' S' R U': M', U2]","[R' S' R: U2, R S' R']","[R' S' R U: M, U2]","","[S' R: S, R2]","[U' R U, S]","[R' S': E, R U2 R']","[R' U2 R, S']","[R' S' R: R E' R', U2]","[R' S': R U2 R', S2]","[u: R U' R', E]","[R' U: U, R S' R']","[R' U' B': R2, E]","[S' R' U: M', U2]","[E, R B2 R']","[R' U': U', R S' R']","","[S' R' U': M, U2]", +"RF","[U': R S' R', U']","[R S' R', U2]","[U: R S' R', U]","[R S' R': S, R2]","[R S' R' U': M', U2]","[R S' R': U2, R' S' R]","[R S' R' U: M, U2]","[S' R': S, R2]","","[U' R' U, S]","[R S' R': R' E R, U2]","[R U2 R', S']","[R S': E', R' U2 R]","[R S': R' U2 R, S2]","","[R U: U, R' S' R]","[E', R' F2 R]","[S' R U: M', U2]","[R U F: R2, E']","[R U': U', R' S' R]","[u': R' U R, E']","[S' R U': M, U2]", "RD","[U, R E R']","[U2, R E R']","[U', R' E' R]","","[S, R F' R']","[U: R F R', S']","[S, R' B R]","[S, U' R U]","[S, U' R' U]","","[S, R F2 R']","[D': S, R' F' R]","[S, R' B2 R]","[U R' F R: S', R2]","[R U R' U', S']","[R' F R, S']","[R' F2 R, S']","[R' F' R, S']","[R B2 R', S']","[R B' R', S']","[R' U R U', S']","[R B R', S']", -"LF","[U, R' E R]","[U2, R' E R]","[U', R' E R]","[R2, S' l' S]","[R F R': U', R' E R]","[S U2 R: E, R2]","[S R B: E, R2]","[R' S': R U2 R', E]","[R S' R': U2, R' E R]","[R F2 R', S]","","[E R' E', l]","[R' B': R U R', E]","[E R' E', l']","[u': R U' R', E']","[S U' R: E, R2]","","[D: R F2 R', S]","[E', R B2 R']","[S U R: E, R2]","[R' u': R U R', E']","[D': R F2 R', S]", -"LU","[U R' U' R, S]","","[U' R' U R, S]","[l: S l S', R2]","[U2: S, R' F' R]","[R' F' R: U, R' S R]","[U2: S, R B R']","[S', R' U2 R]","[S', R U2 R']","[D': R' F' R, S]","[l, S R' S']","","[l', S R S']","[D: R' F' R, S]","[R, U' M' U]","[R' F R, S]","[R' F2 R, S]","[R' F' R, S]","[R B2 R', S]","[S', R U' R']","[R', U' M' U]","[R B R', S]", -"LB","[U, R E' R']","[U2, R E' R']","[U', R E' R']","[R2, S' l S]","[S R' F': E', R2]","[S U2 R': E', R2]","[R' B' R: R E' R', U]","[R' S' R: U2, R E' R']","[R S': R' U2 R, E']","[R' B2 R, S]","[R' B': E, R U R']","[E' R E, l']","","[E' R E, l]","[R U: R' U' R, E']","[S U' R': E', R2]","[E, R' F2 R]","[D: R' B2 R, S]","","[S U R': E', R2]","[u: R' U' R, E]","[D': R' B R, S]", +"LF","[U, R' E R]","[U2, R' E R]","[U', R' E R]","[R2, S' l' S]","[R F R': R' E R, U']","[S U2 R: E, R2]","[S R B: E, R2]","[R' S': R U2 R', E]","[R S' R': U2, R' E R]","[R F2 R', S]","","[E R' E', l]","[R' B': R U R', E]","[E R' E', l']","[u': R U R', E']","[S U' R: E, R2]","","[D: R F2 R', S]","[E', R B2 R']","[S U R: E, R2]","[R' U': R U R', E]","[D': R F2 R', S]", +"LU","[S: R E R', U]","","[S: R E R', U']","[l: S l S', R2]","[U2: S, R' F' R]","[R' F' R: U, R' S R]","[U2: S, R B R']","[S', R' U2 R]","[S', R U2 R']","[D': R' F' R, S]","[l, E R' E']","","[l', E' R E]","[D: R' F' R, S]","[R, U' M' U]","[R' F R, S]","[R' F2 R, S]","[R' F' R, S]","[R B2 R', S]","[S', R U' R']","[R', U' M' U]","[R B R', S]", +"LB","[U, R E' R']","[U2, R E' R']","[U', R E' R']","[R2, S' l S]","[S R' F': E', R2]","[S U2 R': E', R2]","[R' B' R: R E' R', U]","[R' S' R: U2, R E' R']","[R S': R' U2 R, E']","[R' B2 R, S]","[R' B': E, R U R']","[E' R E, l']","","[E' R E, l]","[R U: R' U' R, E']","[S U' R': E', R2]","[E, R' F2 R]","[D: R' B2 R, S]","","[S U R': E', R2]","[u: R' U' R, E]","[D': R' B2 R, S]", "LD","[U': R' S R, U']","[R' S R, U2]","[U: R' S R, U]","[R': E l E', R2]","[R' S R U': M', U2]","","[R' S R U: M, U2]","[R' S: R U2 R', S2]","[R S: R' U2 R, S2]","[U R' F R': S', R2]","[l', E R' E']","[D: S, R' F' R]","[l, E' R E]","","[E' l' E, R']","[R': F, R' S' R]","[R': F2, R' S' R]","[D: S', R' F' R]","[R: B2, R S' R']","[R: B', R S' R']","[E l E', R]","[D': S', R B R']", -"FR","[u: R' E R, U']","[S' l2 S, R]","[E': U', R' E R]","[R U2 R': S, R2]","[U' M2 U, R]","[U R' U': S', R2]","[U M2 U', R]","[U: R U' R', E']","","[S', R U R' U']","[U': R U R', E]","[U' M' U, R]","[R u: R' U' R, E]","[R', E' l' E]","","[S': U, R S' R']","[U R' U' R: E, R2]","[U M' U', R]","[U R' U' R': E', R2]","[S': U', R S' R']","[U2 R': S, R2]","[U' M U, R]", +"FR","[U': R' S2 R, U']","[R' S2 R, U2]","[U: R' S2 R, U]","[R U2 R': S, R2]","[U' M2 U, R]","[U R' U': S', R2]","[U M2 U', R]","[U: R U' R', E']","","[S', R U R' U']","[U': R U R', E]","[U' M' U, R]","[R u: R' U' R, E]","[R', E' l' E]","","[S': U, R S' R']","[U R' U' R: E, R2]","[U M' U', R]","[U R' U' R': E', R2]","[S': U', R S' R']","[U2 R': S, R2]","[U' M U, R]", "FU","[M', R U R' U']","[M U: M', U2]","","[r U' R': S, R2]","[R' F': E, R U' R']","[R F R': S', R2]","[r U' r' U: M, U2]","[R' U: R S' R', U]","[R U: R' S' R, U]","[S', R' F R]","[S U' R': E, R2]","[S, R' F R]","[S U' R: E', R2]","[R': R' S' R, F]","[S': R S' R', U]","","R E R' U' R' E2 R U R' E R","[D: S', R' F R]","[R F: R2, E']","[U' M' U: M, U2]","[S': R' S' R, U]","[D': S', R' F R]", -"FL","[U, R E R2 E' R]","[l', S R2 S']","[U', R E R2 E' R]","[R': E, R2]","[D R': E, R2]","[l': S R2 S', l']","[D' R': E, R2]","[S' R': R' E R, U2]","[R' F2 R, E']","[S', R' F2 R]","","[S, R' F2 R]","[R' F2 R, E]","[R': R' S' R, F2]","[U R' U' R': E, R2]","R' E' R U' R' E2 R U R E' R'","","[D: S', R' F2 R]","[L: S', L2]","[R' B': E, R2]","[U R U' R': E, R2]","[R' B: R2, E]", -"FD","U' M' U' M U' M' U' M","[M U': M, U2]","[M': R U' R' U]","[S' U': M', U2]","","[R F' R': S', R2]","[r U r' U: M, U2]","[S' R' U': M', U2]","[S' R U': M', U2]","[S', R' F' R]","[D: S, R F2 R']","[S, R' F' R]","[D: S, R' B2 R]","[D: R' F' R, S']","[R, U M' U']","[D: R' F R, S']","[D: R' F2 R, S']","","[R F': R2, E']","[D: R B' R', S']","[R', U M' U']","[M' U M, d2]", -"BL","[U, R' E' R2 E R']","[l, S R2 S']","[U', R' E' R2 E R']","[R: E', R2]","[D R: E', R2]","[l: S R2 S', l]","[D' R: E', R2]","[R B2 R', E]","[S' R: R E' R', U2]","[S', R B2 R']","[R B2 R', E']","[S, R B2 R']","","[R: R S' R', B2]","[U R' U' R: E', R2]","[R F: E', R2]","[L': S', L2]","[R F': E', R2]","","R E R' U R E2 R' U' R' E R","[U R U' R: E', R2]","[D': S', R B2 R']", +"FL","[U, R E2 R']","[U2, R E2 R']","[U', R E2 R']","[R': E, R2]","[D R': E, R2]","[l': S R2 S', l']","[D' R': E, R2]","[R' U' B': E, R2]","[R' F2 R, E']","[S', R' F2 R]","","[S, R' F2 R]","[R' F2 R, E]","[R': R' S' R, F2]","[U R' U' R': E, R2]","R' E' R U' R' E2 R U R E' R'","","[D: S', R' F2 R]","[L: S', L2]","[R' B': E, R2]","[U R U' R': E, R2]","[R' B: E, R2]", +"FD","U' M' U' M U' M' U' M","[M U': M, U2]","[M', R U' R' U]","[S' U': M', U2]","","[R F' R': S', R2]","[r U r' U: M, U2]","[S' R' U': M', U2]","[S' R U': M', U2]","[S', R' F' R]","[D: S, R F2 R']","[S, R' F' R]","[D: S, R' B2 R]","[D: R' F' R, S']","[R, U M' U']","[D: R' F R, S']","[D: R' F2 R, S']","","[R F': R2, E']","[D: R B' R', S']","[R', U M' U']","[M' U M, d2]", +"BL","[U, R' E2 R]","[U2, R' E2 R]","[U', R' E2 R]","[R: E', R2]","[D R: E', R2]","[l: S R2 S', l]","[D' R: E', R2]","[R B2 R', E]","[R U F: E', R2]","[S', R B2 R']","[R B2 R', E']","[S, R B2 R']","","[R: R S' R', B2]","[U R' U' R: E', R2]","[R F: E', R2]","[L': S', L2]","[R F': E', R2]","","R E R' U R E2 R' U' R' E R","[U R U' R: E', R2]","[D': S', R B2 R']", "BU","","[M' U': M, U2]","[M, R' U' R U]","[r' U R: S, R2]","[r' U r U': M', U2]","[R' B' R: S', R2]","[U D': R' F R, S']","[R' U': R S' R', U']","[R U': R' S' R, U']","[S', R B' R']","[S U R': E, R2]","[R U' R', S']","[S U R: E', R2]","[R: R S' R', B']","[S': R S' R', U']","[U' M' U': M, U2]","[R' B': R2, E]","[D: S', R B' R']","R' E' R U R E2 R' U' R E' R'","","[S': R' S' R, U']","[D': S', R B' R']", -"BR","[E: U, R E' R']","[S' l2 S, R']","[u': R E' R', U]","[R' U2 R: S, R2]","[U' M2 U, R']","[U R U': S', R2]","[U M2 U', R']","","[U': R' U R, E]","[S', R' U R U']","[R' u': R U R', E']","[U' M' U, R']","[U: R' U' R, E']","[R, E l E']","[U2 R: S, R2]","[S': U, R' S' R]","[U R U' R: E, R2]","[U M' U', R']","[U R U' R': E', R2]","[S': U', R' S' R]","","[U' M U, R']", -"BD","[M, R' U' R U]","[M' U: M', U2]","[D': U', R E R']","[S' U: M, U2]","[r' U' r U': M', U2]","[R' B R: S', R2]","","[S' R' U: M, U2]","[S' R U: M, U2]","[S', R B R']","[D': S, R F2 R']","[S, R B R']","[D': S, R' B2 R]","[D': R B R', S']","[R, U' M U]","[D': R' F R, S']","[R' B: R2, E]","[M U' M', d2]","[D': R B2 R', S']","[D': R B' R', S']","[R', U' M U]","", +"BR","[U': R S2 R', U']","[R S2 R', U2]","[U: R S2 R', U]","[R' U2 R: S, R2]","[U' M2 U, R']","[U R U': S', R2]","[U M2 U', R']","","[U': R' U R, E]","[S', R' U R U']","[R' u': R U R', E']","[U' M' U, R']","[U: R' U' R, E']","[R, E l E']","[U2 R: S, R2]","[S': U, R' S' R]","[U R U' R: E, R2]","[U M' U', R']","[U R U' R': E', R2]","[S': U', R' S' R]","","[U' M U, R']", +"BD","[M, R' U R U']","[M' U: M', U2]","[D': U', R E R']","[S' U: M, U2]","[r' U' r U': M', U2]","[R' B R: S', R2]","","[S' R' U: M, U2]","[S' R U: M, U2]","[S', R B R']","[D': S, R F2 R']","[S, R B R']","[D': S, R' B2 R]","[D': R B R', S']","[R, U' M U]","[D': R' F R, S']","[R' B: R2, E]","[M U' M', d2]","[D': R B2 R', S']","[D': R B' R', S']","[R', U' M U]","", diff --git a/src/speedcubing/coordinates/coordinates.md b/src/speedcubing/coordinates/coordinates.md @@ -276,5 +276,5 @@ See [fst.c](https://git.tronto.net/nissy-nx/file/src/fst.c.html#l113) for an implementation. For these reasons, I am not using the coordinate approach described here -for my new work-in-progress solver (temporarily named -[h48](https://git.tronto.net/h48)). +for my new work-in-progress solver (which can be found +[here](https://git.tronto.net/nissy-core)). diff --git a/src/speedcubing/figure-it-out/comm1.svg b/src/speedcubing/figure-it-out/comm1.svg @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8"> - <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/> - <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1"> - <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/> - <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/> - <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/> - </g> - <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round"> - <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/> - <polygon fill="#FFA100" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/> - <polygon fill="#0000F2" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/> - <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/> - <polygon fill="#FEFE00" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/> - <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/> - <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/> - <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/> - <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/> - <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/> - <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/> - <polygon fill="#EE0000" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/> - <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/> - <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/> - <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/> - <polygon fill="#EE0000" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/> - <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/> - <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/> - </g> -</svg> -\ No newline at end of file diff --git a/src/speedcubing/figure-it-out/comm2.svg b/src/speedcubing/figure-it-out/comm2.svg @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8"> - <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/> - <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1"> - <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/> - <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/> - <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/> - </g> - <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round"> - <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/> - <polygon fill="#FFA100" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/> - <polygon fill="#0000F2" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/> - <polygon fill="#EE0000" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/> - <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/> - <polygon fill="#FEFE00" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/> - <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/> - <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/> - <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/> - <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/> - <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/> - <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/> - <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/> - <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/> - <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/> - <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/> - <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/> - </g> -</svg> -\ No newline at end of file diff --git a/src/speedcubing/figure-it-out/edgecomm.svg b/src/speedcubing/figure-it-out/edgecomm.svg @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8"> - <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/> - <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1"> - <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/> - <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/> - <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/> - </g> - <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round"> - <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/> - <polygon fill="#EE0000" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/> - <polygon fill="#EE0000" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/> - <polygon fill="#00D800" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/> - <polygon fill="#EE0000" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/> - <polygon fill="#00D800" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/> - <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/> - <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/> - <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/> - <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/> - <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/> - <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/> - <polygon fill="#00D800" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/> - <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/> - <polygon fill="#FFA100" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/> - <polygon fill="#00D800" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/> - <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/> - <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/> - </g> -</svg> -\ No newline at end of file diff --git a/src/speedcubing/figure-it-out/face.png b/src/speedcubing/figure-it-out/face.png Binary files differ. diff --git a/src/speedcubing/figure-it-out/figure-it-out.md b/src/speedcubing/figure-it-out/figure-it-out.md @@ -1,3 +1,5 @@ +<script src="https://cdn.cubing.net/v0/js/cubing/twisty" type="module"></script> + # Rubik's cube: how to figure it out So you decided to try and solve a @@ -89,7 +91,7 @@ say that two or more adjacent pieces form a block when adjacent stickers of different pieces have the same color. For example, the simplest kind of block is a **pair**, that you can see in the picture below: - + To be precise, the one above is a coner-edge pair. There are also center-edge pairs, consisting of a center and an edge, but they are rarely referred to @@ -99,11 +101,11 @@ A more complex example of a block is a **layer**, which is the result of the first two steps of the classic "layer by layer" method. This one is a correctly solved layer: - + On the other hand, this is **not a layer**: - + It is worth pausing here to reflect a bit. The last two pictures both clearly show a solved white face, don't they? For a most people, they @@ -124,6 +126,14 @@ If you want some more hints, Ryan Heise's website contains some nice examples about building blocks in his [fundamental techniques page](https://www.ryanheise.com/cube/fundamental_techniques.html). +*Note: I have updated this page to use [twizzle](https://alpha.twizzle.net) +applets instead of simple pictures. These applets can be played to show the +solution. To experiment around with this solution, click on +the TW at the bottom right to open them in +[twizzle](https://alpha.twizzle.net/). In this way you can also +check which moves correspond to the written notation, if you +are not familiar with it.* + ## Commutators *In the rest of this page I am going to use the standard @@ -161,7 +171,13 @@ If you have not done it already, you should have a look at my page on Suppose that you manage, via blockbuilding, to reach the following state: - +<center> +<twisty-player experimental-setup-alg="[U, R' D R]" +alg="R' D R // Insertion + U // Interchange + R' D' R // Inverse insertion + U' // Inverse interchange"></twisty-player> +</center> First of all, this would be an amazing achievement! The whole cube is solved except for three corners. The bottom-left corner (only one red @@ -220,21 +236,28 @@ the insertion sequence; then we have D', the inverse of the second move; and finally R, the inverse of the first move of the insertion sequence. * **U'**: the inverse of the interchange move. -To help understanding all of this, you can visualize this commutator -[alg.cubing.net](https://alg.cubing.net/?setup=%5BU,_R-DR%5D&alg=R-_D_R_%2F%2FInsertion%0AU_%2F%2FInterchange%0AR-_D-_R_%2F%2FInverse_insertion%0AU-_%2F%2FInverse_interchange). - **Note:** looking at the position of the pieces is not enough to determine a correct commutator to permute them. Their **orientation** is also important. For example, consider the following case: - +<center> +<twisty-player experimental-setup-alg="[R', U L' U']" +alg="R' D R // Insertion + U // Interchange + R' D' R // Inverse insertion + U' // Inverse interchange"></twisty-player> +</twisty-player> +</center> The three corners are permuted in exactly the same way, so everything we said above could be repeated word by word, move by move. However, if you apply the commutator we constructed to this case, you'll get -something like this: +something like this (you can also see this by playing the applet above): - +<center> +<twisty-player experimental-setup-alg="[U, R' D' R D R' D' R]"> +</twisty-player> +</center> What's wrong here? Well, obviously the cube is not solved. All the pieces are in their correct position, but two corners are twisted in place! @@ -246,7 +269,13 @@ but sometimes it is important to keep track of both. Let's highlight the difference between the two 3-cycles. In the first one: - +<center> +<twisty-player experimental-setup-alg="[U, R' D R]" +alg="R' D R // Insertion + U // Interchange + R' D' R // Inverse insertion + U' // Inverse interchange"></twisty-player> +</center> 1. The red-green-white corner must go to the place of the white-red-blue one, *with the white sticker of the first going to the place of the white sticker @@ -260,7 +289,14 @@ white sticker of the latter*. While in the second case: - +<center> +<twisty-player experimental-setup-alg="[R', U L' U']" +alg="U L' U' // Insertion + R' // Interchange + U L U' // Inverse insertion + R // Inverse interchange"></twisty-player> +</twisty-player> +</center> 1. The red-green-white corner must go to the place of the white-red-blue one, *with the white sticker of the first going to the place of the* **blue** *sticker @@ -281,16 +317,19 @@ white sticker of the red-green-white corner to the position of the red sticker of the white-red-blue one, while it should move it to the position of the blue sticker! -I won't repeat the whole construction for the second commutator, -but you can visualize a solution -[here](https://alg.cubing.net/?setup=%5BR-,_UL-U-%5D&alg=U_L-_U-_%2F%2FInsertion%0AR-_%2F%2FInterchange%0AU_L_U-_%2F%2FInverse_insertion%0AR_%2F%2FInverse_interchange). - ### Edge commutators So far I have only talked about *corner* commutators, but what if you are also left with some unsolved edges? For example, consider this case: - +<center> +<twisty-player experimental-setup-alg="[L' U2 L, E']" +alg="E' // Interchange + L' U2 L // Insertion + E // Inverse interchange + L' U2 L // Inverse insertion"></twisty-player> +</twisty-player> +</center> The picture shows a 3-cycle of edges. You might think that the same reasoning can be applied and that you can use commutators to solve @@ -301,7 +340,7 @@ Let's see how to solve the case above. As interchange move, you can use the **inner-layer move** E' (check out the [notation page](../notation) if you are unfamiliar with these). The insertion sequence to be used with it is L' U2 L. Putting everything together, you get -[E' L' U2 L E L' U2 L](https://alg.cubing.net/?setup=%5BL-U2L,E-%5D&alg=E-_%2F%2FInterchange%0AL-_U2_L_%2F%2FInsertion%0AE_%2F%2FInverse_interchange%0AL-_U2_L_%2F%2FInverse_insertion). +E' L' U2 L E L' U2 L. ### Commutators with set-up moves @@ -323,7 +362,15 @@ you need to use **set-up moves**, also known as Consider the following case: - +<center> +<twisty-player experimental-setup-alg="[L: [R D2 R', U']]" +alg="L // Set-up + U' // Interchange + R D2 R' // Insertion + U // Inverse interchange + R D2 R' // Inverse insertion + L' // Inverse set-up"></twisty-player> +</center> No matter how much you try, you are not going to find valid interchange and insertion moves as above. The fundamental problem is that you would @@ -353,9 +400,6 @@ putting it all together you get: **Note:** in this case the insertion coincides with its inverse. This can happen and there is nothing particular about it. -As usual, you can visualize the final result on -[alg.cubing.net](https://alg.cubing.net/?setup=L2B2R-F-RB2R-FRL2&alg=L_%2F%2FSet%26%2345%3Bup%0AU-_%2F%2FInterchange%0AR_D2_R-_%2F%2FInsertion%0AU_%2F%2FInverse_interchange%0AR_D2_R-_%2F%2FInverse_insertion%0AL-_%2F%2FInverse_set%26%2345%3Bup) - ## Conclusion With what you have learned so far, you can now try and solve the Rubik's diff --git a/src/speedcubing/figure-it-out/layer.png b/src/speedcubing/figure-it-out/layer.png Binary files differ. diff --git a/src/speedcubing/figure-it-out/pair.png b/src/speedcubing/figure-it-out/pair.png Binary files differ. diff --git a/src/speedcubing/figure-it-out/setup.svg b/src/speedcubing/figure-it-out/setup.svg @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8"> - <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/> - <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1"> - <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/> - <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/> - <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/> - </g> - <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round"> - <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/> - <polygon fill="#0000F2" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/> - <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/> - <polygon fill="#FFA100" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/> - <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/> - <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/> - <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/> - <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/> - <polygon fill="#EE0000" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/> - <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/> - <polygon fill="#EE0000" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/> - <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/> - <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/> - <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/> - <polygon fill="#00D800" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/> - <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/> - <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/> - </g> -</svg> -\ No newline at end of file diff --git a/src/speedcubing/figure-it-out/twist.svg b/src/speedcubing/figure-it-out/twist.svg @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8"> - <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/> - <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1"> - <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/> - <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/> - <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/> - </g> - <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round"> - <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/> - <polygon fill="#0000F2" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/> - <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/> - <polygon fill="#00D800" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/> - <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/> - <polygon fill="#FFFFFF" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/> - <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/> - <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/> - <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/> - <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/> - <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/> - <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/> - <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/> - <polygon fill="#EE0000" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/> - <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/> - <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/> - <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/> - <polygon fill="#00D800" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/> - <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/> - <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/> - </g> -</svg> -\ No newline at end of file diff --git a/top.html b/top.html @@ -2,7 +2,7 @@ <html lang="en"> <head> <title> TITLE | Sebastiano Tronto </title> - <meta name="viewport" content="width=device-width" /> + <meta name="viewport" content="width=device-width"> <link rel="stylesheet" type="text/css" href="/style-5.css"> <link rel="icon" href="/favicon.png"> <meta charset="utf-8">