[{"content":"","permalink":"https://rohith.net/reviews/buckshot/","summary":"This game makes you feel like a badass while gambling your life away for in-game USD dollars. Relatively short though. Kinda felt bad for the dealer sometimes.","title":"Buckshot Roulette"},{"content":"","permalink":"https://rohith.net/reviews/celeste/","summary":"Really amazing, finished the base game, yet to do the additional content.","title":"Celeste"},{"content":"","permalink":"https://rohith.net/reviews/whiplash/","summary":"I feel like I lack the musical literacy required to comprehend a lot of the more technical stuff in the movie. The final scene was pretty awesome though.","title":"Whiplash"},{"content":"","permalink":"https://rohith.net/reviews/obsession/","summary":"Phenomenal acting and amazing sound design. A lot of the elements severely lean into uncanny valley territory which really creeps me out.","title":"Obsession"},{"content":"","permalink":"https://rohith.net/reviews/got/","summary":"Has been great so far!","title":"A Game of Thrones"},{"content":"","permalink":"https://rohith.net/reviews/nine-sols/","summary":"Played this right after Silksong. Absolute banger of a game with the most satisfying combat mechanics. Exploration and some instances of platforming were the only weak-ish parts.","title":"Nine Sols"},{"content":"","permalink":"https://rohith.net/reviews/silksong/","summary":"Stunningly beautiful but unforgiving at times. Might be my favourite game of the year.","title":"Hollow Knight: Silksong"},{"content":" Introduction Recently, me and my friend Shivang got selected for KDE\u0026rsquo;s mentorship programme, Season of KDE 2026.\nhttps://mentorship.kde.org/blog/2026-01-21-sok-26-welcome/#implement-font-subsetting-when-saving-files-in-okular\nOur proposal included implementing font subsetting in their PDF reader, Okular.\nA Brief Overview of Fonts Font files are of many extensions; the most common ones include TrueType (.ttf) and OpenType (.otf) which are binary files containing data organized in specific tables.\nBoth of which are really similar (they both share the same structure involving tables for storing certain types of data). They primarily differ in the fact that TrueType uses quadratic Bézier curves while OpenType uses cubic ones.\nAlso, TrueType was developed by Apple in the late 80s and OpenType was developed by Microsoft and Adobe. It was derived from the original TrueType format and is able to contain both TrueType and PostScript data.\nThis awesome YouTube video covers the history of fonts really well, starting from bitmaps and going all the way upto vector fonts. Also, Sebastian Lague\u0026rsquo;s video on fonts helped a lot to understand the format of TTF files. I highly suggest giving it a watch!\nHere are some of the important tables in TTF/OTF formats relevant to font subsetting:\nName Purpose glyf Stores visual outlines of the characters. loca Contains the addresses to the relevant glyphs from the glyf table. cmap Maps unicode value (like U+0042 for \u0026lsquo;B\u0026rsquo;) to a glyph ID. hmtx Stores the width of the character and the amount of whitespace to the left of it. head Global font header (contains info about the font\u0026rsquo;s version etc.) and its checksum. One thing to note is that in TTF files there are simple glyphs (like a) and composite glyphs (like é) formed by combining multiple simple glyphs.\nSide note: hmtx is different from kerning (which uses the kern table) because hmtx defines how much width the character actually takes up and the left side bearing (initial space) while kerning defines how much space there is between two characters. Kerning takes priority when specific characters are grouped together to make it look more natural (like \u0026ldquo;AV\u0026rdquo; or \u0026ldquo;To\u0026rdquo;).\nAnyhow, we are getting a bit sidetracked as most of the actual font (and by extension, subsetting) logic is abstracted away by the use of the HarfBuzz API.\nWhat is Font Subsetting? Font subsetting includes stripping away unused characters from a bigger, original font file and essentially creating a subset of only the characters needed.\nFor example, a JetBrainsMono-Regular.ttf downloaded from their website is almost 114 kb. A trimmed down version of this font file subsetted for the string \u0026ldquo;hello\u0026rdquo; (so it only contains h, e, l and o) is around 1.7 kb.\nHow is this relevant to a PDF reader?\nWhen new text annotations are added in Okular, the whole font file is embedded into the PDF which makes the PDF file size huge (especially for fonts that contain a lot of glyphs). Font subsetting completely mitigates this issue by reducing the size of the embedded font file.\nWhile the proposal implies font subsetting in Okular, most of the work is to be done in Poppler, the PDF backend and rendering engine which Okular uses.\nExample Subsetting Function (using HarfBuzz) After a lot of planning, we decided to use the hb and hb-subset APIs provided by HarfBuzz.\nHere\u0026rsquo;s an example function subset_font() which takes in an input file path, an output file path and the text to generate the subsetted font file.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include \u0026lt;hb-subset.h\u0026gt; #include \u0026lt;hb.h\u0026gt; // for hb_font_t and hb_shape void subset_font(const char *in, const char *out, const std::string \u0026amp;text) { // map the font file on disk into a HarfBuzz blob on memory hb_blob_t *blob = hb_blob_create_from_file(in); // new face object from the blob hb_face_t *face = hb_face_create(blob, 0); // new font object from specified face hb_font_t *font = hb_font_create(face); // new subset input object (fails if hb-subset is missing) hb_subset_input_t *input = hb_subset_input_create_or_fail(); // hold the input text and its properties before shaping hb_buffer_t *buf = hb_buffer_create(); // retain GID mappings hb_subset_input_set_flags(input, HB_SUBSET_FLAGS_RETAIN_GIDS); // shape text directly to GIDs hb_buffer_add_utf8(buf, text.c_str(), -1, 0, -1); hb_buffer_guess_segment_properties(buf); hb_shape(font, buf, nullptr, 0); // add GIDs to the subset input unsigned int count; hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buf, \u0026amp;count); for (unsigned int i = 0; i \u0026lt; count; i++) hb_set_add(hb_subset_input_glyph_set(input), info[i].codepoint); // generate a subsetted font file and save on disk if (hb_face_t *s_face = hb_subset_or_fail(face, input)) { hb_blob_t *res = hb_face_reference_blob(s_face); unsigned int len; const char *data = hb_blob_get_data(res, \u0026amp;len); if (FILE *f = fopen(out, \u0026#34;wb\u0026#34;)) { fwrite(data, 1, len, f); fclose(f); } hb_blob_destroy(res); hb_face_destroy(s_face); } // cleanup hb_buffer_destroy(buf); hb_font_destroy(font); hb_subset_input_destroy(input); hb_face_destroy(face); hb_blob_destroy(blob); } Explaining the HarfBuzz types hb_blob_t: From the HarfBuzz manual:\nData type for blobs. A blob wraps a chunk of binary data and facilitates its lifecycle management between a client program and HarfBuzz.\nA blob is a wrapper for raw binary data (in this case, the font file). This abstracts away the memory management aspect of loading the data, as in, parts of the font file can be lazy loaded as required.\nhb_face_t: From the HarfBuzz manual:\nA font face can be created from a binary blob using hb_face_create(). The face index is used to select a face from a binary blob that contains multiple faces. For example, a binary blob that contains both a regular and a bold face can be used to create two font faces, one for each face index.\n1 hb_face_t *face = hb_face_create(blob, 0); Here, the blob is passed as the first argument and the second argument is the faceIndex. Since we\u0026rsquo;re only dealing with .ttf files here, we can safely pass the face index as 0. However this would differ for font collections (.ttc files).\n1 2 3 4 5 6 7 8 Single Font File (.ttf): Font Collection (.ttc): _______________________ ________________________ | | | | | faceIndex=0 | | faceIndex=0: Regular | | (only one) | | faceIndex=1: Bold | |_______________________| | faceIndex=2: Italic | |________________________| hb_font_t: From the HarfBuzz manual:\nData type for holding fonts.\nAn hb_font_t is essentially an hb_face_t with added instance parameters like scale, PPEM (pixels per EM), PTEM (points per EM) and variations (eg: Weight=700, Width=120 etc.).\nhb_subset_input_t: From the HarfBuzz manual:\nThings that change based on the input. Characters to keep, etc.\nhb_buffer_t: From the HarfBuzz manual:\nThe main structure holding the input text and its properties before shaping, and output glyphs and their information after shaping.\nThis object eventually contains the string of unicode characters from the inputted text (which then gets used to generate the subsetted font). After shaping, (hb_shape()), the buffer holds the glyph IDs and other glyph data.\nRetaining GID mappings HarfBuzz provides a flag which can be set to avoid remapping the glyph IDs of the characters in the subsetted file.\n1 hb_subset_input_set_flags(input, HB_SUBSET_FLAGS_RETAIN_GIDS); From the HarfBuzz manual:\nHB_SUBSET_FLAGS_RETAIN_GIDS If set glyph indices will not be modified in the produced subset. If glyphs are dropped their indices will be retained as an empty glyph. Remember the cmap table from earlier? Because the GIDs (glyph IDs) have changed, it must be rebuilt so that the unicode codepoints point to new, lower indices.\nPassing this flag enables us to retain the original GIDs. Poppler\u0026rsquo;s code internally requires the GIDs to not be remapped (more on this later). However, there is a caveat to this.\nHere\u0026rsquo;s an example:\nBelow is a comparison of two different subsetted TTF files from a JetBrainsMono-Regular TTF file, with the retain GID mappings flag enabled for test1.ttf and disabled for test.ttf, for the string \u0026ldquo;hello\u0026rdquo;.\nNotice the difference in the number of glyphs: However, both of them contain the exact same number of characters: This is because when retaining the GID mappings, HarfBuzz \u0026ldquo;pads\u0026rdquo; the file with a bunch of empty glyphs. Hence the output font file size is comparatively bigger.\nConfiguring the development environment Setting up kde-builder After looking into the process of developing KDE applications, we found out that KDE provides their own build automation tool called kdesrc-build.\nHowever, this ended up being a dead end as after finishing all the setup, we found out about kde-builder (the official successor and reimplementation of kdesrc-build).\nAfter setting up the tool, we also configured Okular and Poppler to use custom source directories:\nIn ~/.config/kde-builder.yaml:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 project poppler: repository: \u0026#34;\u0026#34; no-src: true source-dir: \u0026#34;\u0026lt;path to src dir\u0026gt;\u0026#34; cmake-options: \u0026gt; -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_GTK_TESTS=OFF -DBUILD_QT5_TESTS=OFF -DBUILD_QT6_TESTS=OFF -DBUILD_CPP_TESTS=OFF -DENABLE_UTILS=OFF -DENABLE_CPP=OFF -DENABLE_GLIB=OFF -DENABLE_QT5=OFF -DENABLE_QT6=ON override okular: no-src: true source-dir: \u0026#34;\u0026lt;path to src dir\u0026gt;\u0026#34; Both of us faced an issue with the documentation checker when we were trying to compile Wayland. This got fixed with:\n1 2 override wayland: meson-options: \u0026#34;-Ddocumentation=false\u0026#34; 1 2 3 4 5 6 7 kde-builder poppler # building poppler for the first time kde-builder okular # building okular for the first time kde-builder --run okular # running okular kde-builder poppler --no-include-dependencies # subsequent poppler builds kde-builder okular --no-include-dependencies # subsequent okular builds Setting up gdb Shivang ended up configuring GDB (GNU Debugger) to be used with Okular and Poppler for better debugging.\nThe following steps were taken:\n1. Changing the cmake-options in kde-builder.yaml from DCMAKE_BUILD_TYPE=RelWithDebInfo to DCMAKE_BUILD_TYPE=Debug.\n2. Forcing a rebuild of Poppler:\n1 kde-builder poppler --refresh-build --no-include-dependencies 3. source ~/kde/build/prefix.sh for loading all the required environment variables.\n4. Running gdb okular.\nLinking the HarfBuzz library in Poppler Since Poppler uses CMake as its build system, I edited CMakeLists.txt to include HarfBuzz as a dependency to enable the use of header files like hb.h and hb-subset.h. As per the suggestion of my mentor, I found out that the way I was linking HarfBuzz was outdated. Hence, I have edited this part of the blog to include the newer way, which uses PkgConfig (instead of target_include_directories).\ncmake/modules/FindHarfBuzz.cmake: 1 2 3 4 5 6 7 include(FindPackageHandleStandardArgs) find_package(PkgConfig REQUIRED) pkg_check_modules(HARFBUZZ IMPORTED_TARGET \u0026#34;harfbuzz\u0026gt;=${HARFBUZZ_VERSION}\u0026#34; harfbuzz-subset) find_package_handle_standard_args(HarfBuzz DEFAULT_MSG HARFBUZZ_LIBRARIES HARFBUZZ_CFLAGS) CMakeLists.txt: 1 set(HARFBUZZ_VERSION \u0026#34;2.0.0\u0026#34;) 1 find_package(HarfBuzz ${HARFBUZZ_VERSION} REQUIRED) 1 set(poppler_LIBS Freetype::Freetype ZLIB::ZLIB PkgConfig::HARFBUZZ) Poppler Integration Relevant Commits Shivang ended up finding the relevant commits related to font embedding in the commit history:\nAnnotations: Make sure we embed fonts for the FreeText annots\nSignatures: Make sure we embed the needed fonts\nForms: Make sure we embedd fonts as needed\nPoppler also contains support for stream compression (to compress font streams in forms), added in this commit.\nRelevant Functions We narrowed down the relevant functions as follows:\nIn poppler/GlobalParams.cc: findSystemFontFileForChar() searches the computer for a font which contains a specific glyph for a character. findSystemFontFileForFamilyAndStyle() finds a font on the computer based on the requested font family and style. supportedFontForEmbedding() checks if the found font type is TrueType (.ttf), TrueType Collection (.ttc), or OpenType (.otf) since those are the only font file types supported by Poppler. In poppler/Form.cc: addFontToDefaultResources() takes the font file as a parameter and embeds it into the PDF\u0026rsquo;s resources. ensureFontsForAllCharacters() checks whether all the relevant characters in the PDF can be displayed by the current font. If not, it starts the font embedding process once again. findFontInDefaultResources() searches for a specific font within a PDF form\u0026rsquo;s default resource dictionary and returns its internal resource name (key) if found. Side note: The default resource dictionary (denoted as /DR) holds the technical definitions for everything the PDF viewer needs to display form data consistently.\nIn poppler/Annot.cc: The type AnnotText handles all the sticky notes. However this is not relevant to us as the system handles the font here. AnnotFreeText handles text typed directly on the page. It includes complex text layouting (layoutText()) to wrap lines and also includes the ability to choose fonts. So for all AnnotFreeText objects, we need to intercept the font stream before it gets embedded into the PDF and then subset it.\nCurrent Implementation (Font Embedding Into Global Resource) When the user creates an annotation, selects a font (eg. Arial) and clicks apply, Poppler does two things:\nIt checks the PDF\u0026rsquo;s global /Resources dictionary. If the font is not there, it takes the font data and writes it to the global resource. Poppler then uses the font stored in the global resource to render the characters on the screen.\nThe appended font is not subsetted. If it were, when the user adds the next annotation, and if the annotation contains characters that are not in the appended font file, then the text would fail to render.\nProposed Implementation (Subsetted Font Embedding Into Annotation\u0026rsquo;s Local Resource) Here, we modify the process to append the subsetted font file to the annotation\u0026rsquo;s local resource instead of the PDF\u0026rsquo;s global resource.\nWe aim to make it such that the process happens in four steps:\nWhen the user selects the font and clicks apply, the entire font file gets appended into the annotation\u0026rsquo;s local resource. As the user adds or delete characters, the full font file gets used to prevent any missing glyphs. When the user saves the PDF, we scan the annotation\u0026rsquo;s text and pass it into the subsetting function which outputs the embedded font file subsetted for that specific string. The original font file is deleted from the annotation\u0026rsquo;s local resource and replaced with the subsetted font file. References https://gitlab.freedesktop.org/poppler/poppler https://youtu.be/BfEvIjTQkIE?si=GSV6CbWuBMePFYnc https://www.youtube.com/watch?v=SO83KQuuZvg https://harfbuzz.github.io/ ","permalink":"https://rohith.net/posts/sok-status-update/","summary":"SoK status update on adding font subsetting to Okular, covering how fonts work and getting HarfBuzz set up with Poppler.","title":"Season of KDE Status Update"},{"content":" This is going to be a relatively short post as I am writing this to shed light on the situation of a bot I built a few years ago called BookBot.\nCurrent Situation As part of Discord\u0026rsquo;s safety policy, verified bots have to be periodically re-verified. The last time the bot was verified was 3 years ago. It used to be added to around ~2850 servers and actively running before Discord enforced re-verification.\nFrom their article:\nThe most noteworthy requirement is that the owner of the development team which owns the app will need to verify their identity through Stripe, our identity verification provider. If you have questions about this, please see our Stripe FAQ. You may be asked to periodically reverify your id with stripe.\nNormally, this would not be an issue but as my team member and co-developer Maks has been inactive for a long time (with no means of contacting him).\nHe is also the owner of the Discord team that owns bookbot (I had transferred the ownership so he could verify the bot back then as I was unable to, this happened 3 years ago).\nThis means that I cannot re-verify the bot without him transferring the ownership back to me. I did contact Discord regarding this, and this was their response:\nIn short, BookBot can no longer be added to new Discord servers until it is re-verified.\nWhat next? For the existing users of BookBot, the main bot is going to be down. However, I have gone ahead and created a second bot which can be added to new servers. Here is the invite link.\nAlso, a special thanks to @codyrocks10 on Discord who kindly decided to provide hosting for the bot.\nThat\u0026rsquo;s it for this blog. Until next time, bye!\n","permalink":"https://rohith.net/misc/the-bookbot-situation/","summary":"\u003chr\u003e\n\u003cp\u003eThis is going to be a relatively short post as I am writing this to shed light on the situation of a bot I built a few years ago called \u003ca href=\"https://github.com/rv178/bookbot\"\u003eBookBot\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"current-situation\"\u003eCurrent Situation\u003c/h2\u003e\n\u003cp\u003eAs part of Discord\u0026rsquo;s safety policy, verified bots have to be periodically re-verified. The last time the bot was verified was 3 years ago.\nIt used to be added to around ~2850 servers and actively running before Discord enforced re-verification.\u003c/p\u003e","title":"The Bookbot Situation"},{"content":" Welcome to my dev-blog series on writing a chess engine. This is part 3. Like always, suggestions are welcome!\nHyperbola quintessence? This is the definition from chessprogramming.org:\nHyperbola Quintessence applies the o^(o-2r)-trick also for vertical or diagonal negative Rays - by reversing the bit-order of up to one bit per rank or byte with a vertical flip aka x86-64 bswap.\nWhy did I go for this approach? Because I thought magic bitboards were too hard for me.\nImplementation 1 2 3 4 5 6 7 8 9 10 11 12 // hyperbola quintessence pub fn hyp_quint(sq: Square, occ: BitBoard, mask: u64) -\u0026gt; BitBoard { let mut forward = occ.0 \u0026amp; mask; let mut reverse = forward.reverse_bits(); forward = forward.wrapping_sub(BitBoard::from_sq(sq).0); reverse = reverse.wrapping_sub(BitBoard::from_sq(sq).0.reverse_bits()); forward ^= reverse.reverse_bits(); forward \u0026amp;= mask; BitBoard(forward) } Where sq is the square the piece is in, occ is the occupancy bitboard, and mask is the attack mask. Why use wrapping_sub() instead of the -= operator? I found that the code sometimes panics if the variable is out of bounds.\nAs I already covered in my previous blog:\n1 2 3 4 5 6 7 8 9 10 11 12 13 // to represent squares #[rustfmt::skip] #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Square { A8, B8, C8, D8, E8, F8, G8, H8, A7, B7, C7, D7, E7, F7, G7, H7, A6, B6, C6, D6, E6, F6, G6, H6, A5, B5, C5, D5, E5, F5, G5, H5, A4, B4, C4, D4, E4, F4, G4, H4, A3, B3, C3, D3, E3, F3, G3, H3, A2, B2, C2, D2, E2, F2, G2, H2, A1, B1, C1, D1, E1, F1, G1, H1, } Square is an enum. The occupancy bitboard gives us the occupied squares (where the rays are blocked). As for the mask, here\u0026rsquo;s an example:\nLet\u0026rsquo;s say we wish to get the attacks in one file. For this, let\u0026rsquo;s assume a rook is on E4:\n1 2 3 4 5 6 7 8 9 10 11 12 13 Hex: 1000000000 Value: 68719476736 8 . . . . . . . . 7 . . . . . . . . 6 . . . . . . . . 5 . . . . . . . . 4 . . . . 1 . . . 3 . . . . . . . . 2 . . . . . . . . 1 . . . . . . . . a b c d e f g h And C4 and G4 are occupied:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 Hex: 4400000000 Value: 292057776128 8 . . . . . . . . 7 . . . . . . . . 6 . . . . . . . . 5 . . . . . . . . 4 . . 1 . . . 1 . 3 . . . . . . . . 2 . . . . . . . . 1 . . . . . . . . a b c d e f g h Now the mask for generating attack on this specific rank would be a bitboard of the 4th rank. That is,\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 Hex: ff00000000 Value: 1095216660480 8 . . . . . . . . 7 . . . . . . . . 6 . . . . . . . . 5 . . . . . . . . 4 1 1 1 1 1 1 1 1 3 . . . . . . . . 2 . . . . . . . . 1 . . . . . . . . a b c d e f g h Now if we execute the hyperbola quintessence function on this, we get all possible attacks for the rook on the 4th rank.\n1 hyp_quint(Square::E4, r, RANKS[4].0).print(); 1 2 3 4 5 6 7 8 9 10 11 12 13 Hex: 6c00000000 Value: 463856467968 8 . . . . . . . . 7 . . . . . . . . 6 . . . . . . . . 5 . . . . . . . . 4 . . 1 1 . 1 1 . 3 . . . . . . . . 2 . . . . . . . . 1 . . . . . . . . a b c d e f g h This is the intended bitboard denoting all possible rank-attacks of a rook on E4 with respect to the occupancy bitboard. Note that we also generate an attack on the blocked bits, as they can be captures (unless they\u0026rsquo;re friendly pieces).\nWe can do the same for generating rook attacks both vertically and horizontally.\n1 2 3 4 5 6 pub fn rook(sq: Square, occ: BitBoard) -\u0026gt; BitBoard { let tr = sq as usize / 8; let tf = sq as usize % 8; BitBoard(hyp_quint(sq, occ, FILES[tf].0).0 | hyp_quint(sq, occ, RANKS[tr].0).0) } Where tr is the target rank, and tf is the target file (for getting the value of file/rank mask).\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pub const RANKS: [BitBoard; 8] = [ BitBoard(255), // 8th rank BitBoard(65280), // 7th rank BitBoard(16711680), // 6th rank BitBoard(4278190080), // 5th rank BitBoard(1095216660480), // 4th rank BitBoard(280375465082880), // 3rd rank BitBoard(71776119061217280), // 2nd rank BitBoard(18374686479671623680), // 1st rank ]; pub const FILES: [BitBoard; 8] = [ BitBoard(72340172838076673), // a file BitBoard(144680345676153346), // b file BitBoard(289360691352306692), // c file BitBoard(578721382704613384), // d file BitBoard(1157442765409226768), // e file BitBoard(2314885530818453536), // f file BitBoard(4629771061636907072), // g file BitBoard(9259542123273814144), // h file ]; For bishops, we can do the same but with diagonal and anti-diagonal masks.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // diagonal masks pub const DIAG: [BitBoard; 15] = [ BitBoard(0x80), BitBoard(0x8040), BitBoard(0x804020), BitBoard(0x80402010), BitBoard(0x8040201008), BitBoard(0x804020100804), BitBoard(0x80402010080402), BitBoard(0x8040201008040201), BitBoard(0x4020100804020100), BitBoard(0x2010080402010000), BitBoard(0x1008040201000000), BitBoard(0x804020100000000), BitBoard(0x402010000000000), BitBoard(0x201000000000000), BitBoard(0x100000000000000), ]; // anti-diagonal masks pub const ANTI_DIAG: [BitBoard; 15] = [ BitBoard(0x1), BitBoard(0x102), BitBoard(0x10204), BitBoard(0x1020408), BitBoard(0x102040810), BitBoard(0x10204081020), BitBoard(0x1020408102040), BitBoard(0x102040810204080), BitBoard(0x204081020408000), BitBoard(0x408102040800000), BitBoard(0x810204080000000), BitBoard(0x1020408000000000), BitBoard(0x2040800000000000), BitBoard(0x4080000000000000), BitBoard(0x8000000000000000), ]; 1 2 3 4 5 6 7 8 9 10 11 12 pub fn bishop(sq: Square, occ: BitBoard) -\u0026gt; BitBoard { let tr = sq as usize / 8; let tf = sq as usize % 8; let diag_index: usize = 7 + tr - tf; let anti_diag_index: usize = tr + tf; BitBoard( hyp_quint(sq, occ, DIAG[diag_index].0).0 | hyp_quint(sq, occ, ANTI_DIAG[anti_diag_index].0).0, ) } And for generating queen attacks, it\u0026rsquo;s even easier since we can take the union of the bishop and rook attack bitboards:\n1 2 3 pub fn queen(sq: Square, occ: BitBoard) -\u0026gt; BitBoard { BitBoard(rook(sq, occ).0 | bishop(sq, occ).0) } That\u0026rsquo;s it for this blog, see you soon!\n","permalink":"https://rohith.net/posts/hyperbola-quintessence-in-rust/","summary":"Generating sliding piece attacks for rooks, bishops and queens with hyperbola quintessence.","title":"Hyperbola Quintessence in Rust"},{"content":" Introduction Hello there! I haven\u0026rsquo;t posted in a while because I was working on my chess engine. Specifically, representing the pieces as bitboards. Now you may ask what a bitboard is. You can use bitboards to represent a chess board in a piece-centric manner. What is so special about bitboards then? Doesn\u0026rsquo;t a piece list do the same thing? Well, representing a bitboard only requires a single unsigned 64 bit integer!\nAs taken from the chessprogramming wiki:\nTo represent the board we typically need one bitboard for each piece-type and color - likely encapsulated inside a class or structure, or as an array of bitboards as part of a position object. A one-bit inside a bitboard implies the existence of a piece of this piece-type on a certain square - one to one associated by the bit-position.\nFor example, a bitboard containing all of white\u0026rsquo;s pawns in the starting position would look like this:\n1 2 3 4 5 6 7 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 1 1 1 1 1 . . . . . . . . As you can observe, the squares containing the pawns have a value of 1 and others have a value of 0. A bitboard representing a particular piece will have 1 in the squares the piece occupies and 0 in all the other squares.\nBefore proceding further, I\u0026rsquo;d like to shed light on the fact that I\u0026rsquo;m fairly new to the chess engine development scene, so all suggestions are welcome!\nImplementation I decided to go with representing all the bitboards in a single position as a struct.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #[derive(Debug, Clone)] struct BitPos { wp: BitBoard, // white pawns wn: BitBoard, // white knights wb: BitBoard, // white bishops wr: BitBoard, // white rooks wq: BitBoard, // white queen wk: BitBoard, // white king bp: BitBoard, // black pawns bn: BitBoard, // black knights bb: BitBoard, // black bishops br: BitBoard, // black rooks bq: BitBoard, // black queen bk: BitBoard, // black king } BitBoard is a struct containing a u64.\n1 2 #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct BitBoard(pub u64); Then I wrote a simple function to generate a bitboard from my piece list array (see fen-string-parsing-in-rust)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fn gen(pieces: \u0026amp;mut [Option\u0026lt;Piece\u0026gt;; 64], compare: char) -\u0026gt; Self { let mut bin_str = String::new(); // reverse pieces array pieces.reverse(); for piece in pieces { if let Some(piece) = piece { if !(piece.symbol == compare) { bin_str.push(\u0026#39;0\u0026#39;); } else { bin_str.push(\u0026#39;1\u0026#39;); } } else { bin_str.push(\u0026#39;0\u0026#39;); } } // convert binary string to decimal (u64) Self(u64::from_str_radix(\u0026amp;bin_str, 2).unwrap()) } And another function to print out the bitboard:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 fn print(\u0026amp;self) { println!(\u0026#34;Value: {}\u0026#34;, \u0026amp;self.0); println!(); let mut x = 8; for rank in 0..8 { print!(\u0026#34;\\x1b[34m{}\\x1b[0m \u0026#34;, x); x -= 1; for file in 0..8 { let square = rank * 8 + file; if self.0 \u0026amp; (1 \u0026lt;\u0026lt; square) \u0026gt;= 1 { print!(\u0026#34;\\x1b[1m1\\x1b[0m \u0026#34;); } else { print!(\u0026#34;\\x1b[38;5;8m0\\x1b[0m \u0026#34;); } } println!(); } println!(); println!(\u0026#34;\\x1b[34m a b c d e f g h\\x1b[0m\u0026#34;); println!(); } And some other functions for convenience:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // generate bitboard from square fn from_sq(square: Square) -\u0026gt; Self { Self(1 \u0026lt;\u0026lt; square as u8) } // init empty bitboard fn empty() -\u0026gt; Self { Self(0) } // set bit at given square (0 -\u0026gt; 1) fn set_bit(\u0026amp;mut self, square: Square) { self.0 |= BitBoard::from_sq(square).0; } // toggle bit at given square fn toggle_bit(\u0026amp;mut self, square: Square) { self.0 ^= BitBoard::from_sq(square).0; } What fascinated me is the fact that how easy it was to perform actions on bitboards.\nI also made an enum to represent the squares (just for convenience and better code readability).\n1 2 3 4 5 6 7 8 9 10 11 12 13 // to represent squares #[rustfmt::skip] #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Square { A8, B8, C8, D8, E8, F8, G8, H8, A7, B7, C7, D7, E7, F7, G7, H7, A6, B6, C6, D6, E6, F6, G6, H6, A5, B5, C5, D5, E5, F5, G5, H5, A4, B4, C4, D4, E4, F4, G4, H4, A3, B3, C3, D3, E3, F3, G3, H3, A2, B2, C2, D2, E2, F2, G2, H2, A1, B1, C1, D1, E1, F1, G1, H1, } Piece attack generation I found this very interesting. I still haven\u0026rsquo;t finished working on move generation for sliding pieces at the time of writing this.\nAnyways, for generating pawn attacks, you can shift the bitboard by 7 and 9 in specific directions to achieve the desired result. That is, capturing diagonally.\nFor example:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const NOT_A_FILE: BitBoard = BitBoard(18374403900871474942); const NOT_H_FILE: BitBoard = BitBoard(9187201950435737471); pub fn east(board: BitBoard, colour: Colour) -\u0026gt; BitBoard { match colour { Colour::White =\u0026gt; BitBoard((board.0 \u0026gt;\u0026gt; 9) \u0026amp; NOT_H_FILE.0), Colour::Black =\u0026gt; BitBoard((board.0 \u0026lt;\u0026lt; 9) \u0026amp; NOT_A_FILE.0), Colour::Undefined =\u0026gt; exit(1), } } pub fn west(board: BitBoard, colour: Colour) -\u0026gt; BitBoard { match colour { Colour::White =\u0026gt; BitBoard((board.0 \u0026gt;\u0026gt; 7) \u0026amp; NOT_A_FILE.0), Colour::Black =\u0026gt; BitBoard((board.0 \u0026lt;\u0026lt; 7) \u0026amp; NOT_H_FILE.0), Colour::Undefined =\u0026gt; exit(1), } } I also a made a function for convenience (lookup pawn attacks for a particular square):\n1 2 3 4 5 6 7 8 9 pub fn lookup(square: Square, colour: Colour) -\u0026gt; BitBoard { let board = BitBoard::from_sq(square); match colour { Colour::White =\u0026gt; BitBoard(east(board, Colour::White).0 | west(board, Colour::White).0), Colour::Black =\u0026gt; BitBoard(east(board, Colour::Black).0 | west(board, Colour::Black).0), Colour::Undefined =\u0026gt; exit(1), } } And another one for all pawns:\n1 2 3 4 5 6 7 pub fn all(board: BitBoard, colour: Colour) -\u0026gt; BitBoard { match colour { Colour::White =\u0026gt; BitBoard(east(board, Colour::White).0 | west(board, Colour::White).0), Colour::Black =\u0026gt; BitBoard(east(board, Colour::Black).0 | west(board, Colour::Black).0), Colour::Undefined =\u0026gt; exit(1), } } Attacks can be generated in the same manner for knights and kings:\nKnights 1 2 3 4 5 6 7 8 9 10 11 noNoWe noNoEa +15 +17 | | noWeWe +6 __| |__+10 noEaEa \\ / \u0026gt;0\u0026lt; __ / \\ __ soWeWe -10 | | -6 soEaEa | | -17 -15 soSoWe soSoEa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 const NOT_HG_FILE: BitBoard = BitBoard(4557430888798830399); const NOT_AB_FILE: BitBoard = BitBoard(18229723555195321596); pub fn no_no_east(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 17) \u0026amp; NOT_A_FILE.0) } pub fn no_no_west(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 15) \u0026amp; NOT_H_FILE.0) } pub fn so_so_east(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 15) \u0026amp; NOT_A_FILE.0) } pub fn so_so_west(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 17) \u0026amp; NOT_H_FILE.0) } pub fn no_ea_east(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 10) \u0026amp; NOT_AB_FILE.0) } pub fn so_ea_east(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 6) \u0026amp; NOT_AB_FILE.0) } pub fn no_we_west(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 6) \u0026amp; NOT_HG_FILE.0) } pub fn so_we_west(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 10) \u0026amp; NOT_HG_FILE.0) } // and then just get the union of all the other boards pub fn knight(sq: Square) -\u0026gt; BitBoard { let board = BitBoard::from_sq(sq); let no_no_east = knight::no_no_east(board); let no_no_west = knight::no_no_west(board); let so_so_east = knight::so_so_east(board); let so_so_west = knight::so_so_west(board); let no_ea_east = knight::no_ea_east(board); let so_ea_east = knight::so_ea_east(board); let no_we_west = knight::no_we_west(board); let so_we_west = knight::so_we_west(board); BitBoard( no_no_east.0 | no_no_west.0 | so_so_east.0 | so_so_west.0 | no_ea_east.0 | so_ea_east.0 | no_we_west.0 | so_we_west.0, ) } Result (knight on e4):\n1 2 3 4 5 6 7 8 9 10 8 . . . . . . . . 7 . . . . . . . . 6 . . . 1 . 1 . . 5 . . 1 . . . 1 . 4 . . . . . . . . 3 . . 1 . . . 1 . 2 . . . 1 . 1 . . 1 . . . . . . . . a b c d e f g h King 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 // north pub fn no(board: BitBoard) -\u0026gt; BitBoard { BitBoard(board.0 \u0026lt;\u0026lt; 8) } // south pub fn so(board: BitBoard) -\u0026gt; BitBoard { BitBoard(board.0 \u0026gt;\u0026gt; 8) } // east pub fn ea(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 1) \u0026amp; NOT_A_FILE.0) } // west pub fn we(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 1) \u0026amp; NOT_H_FILE.0) } // north east pub fn no_ea(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 9) \u0026amp; NOT_A_FILE.0) } // north west pub fn no_we(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026lt;\u0026lt; 7) \u0026amp; NOT_H_FILE.0) } // south east pub fn so_ea(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 7) \u0026amp; NOT_A_FILE.0) } // south west pub fn so_we(board: BitBoard) -\u0026gt; BitBoard { BitBoard((board.0 \u0026gt;\u0026gt; 9) \u0026amp; NOT_H_FILE.0) } pub fn king(sq: Square) -\u0026gt; BitBoard { let board = BitBoard::from_sq(sq); let no = king::no(board); let so = king::so(board); let ea = king::ea(board); let we = king::we(board); let no_ea = king::no_ea(board); let no_we = king::no_we(board); let so_ea = king::so_ea(board); let so_we = king::so_we(board); BitBoard(no.0 | so.0 | ea.0 | we.0 | no_ea.0 | no_we.0 | so_ea.0 | so_we.0) } Result (king on e4):\n1 2 3 4 5 6 7 8 9 10 8 . . . . . . . . 7 . . . . . . . . 6 . . . . . . . . 5 . . . 1 1 1 . . 4 . . . 1 . 1 . . 3 . . . 1 1 1 . . 2 . . . . . . . . 1 . . . . . . . . a b c d e f g h That\u0026rsquo;s it for this blog, see you soon!\nReferences chessprogramming.org/Bitboards Chess engine in C ","permalink":"https://rohith.net/posts/bitboards-in-rust/","summary":"Representing the pieces of a chess board as bitboards (using 64-bit integers) and using them to generate piece attacks.","title":"Bitboards in Rust"},{"content":" Yew Like a lot of people, I think WASM is the future. Yew is an amazing frontend framework that helps in building web apps using WASM.\nIt\u0026rsquo;s incredibly easy to use and the performance of the app is EXTREMELY fast! I\u0026rsquo;m currently re-writing Snowstry with yew.rs for the frontend (switching from NextJS). Here\u0026rsquo;s the repo link.\nYew + Tailwind Setting up yew with tailwind was very easy as well. If you don\u0026rsquo;t know what tailwind is, here\u0026rsquo;s the link to their website.\nFirst things first, we need the tailwindcss binary for compiling the output css file.\nI prefer using yarn over npm, but it\u0026rsquo;s upto you.\n1 yarn global add tailwindcss Now let\u0026rsquo;s setup a tailwind config file:\n1 2 3 4 5 module.exports = { content: [\u0026#34;./src/**/*.rs\u0026#34;, \u0026#34;./index.html\u0026#34;, \u0026#34;./public/css/*.css\u0026#34;], theme: {}, plugins: [], }; Directory structure:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ├── Cargo.toml +\t├── index.html (index.html in root) ├── public │ ├── css +\t│ │ ├── main.css (global css file) +\t│ │ └── out.css (output css) ├── src │ ├── components │ │ ├── mod.rs │ │ ├── nav.rs │ ├── main.rs │ └── pages │ ├── home.rs │ ├── mod.rs │ └── notfound.rs +\t├── tailwind.config.js (tailwind config) └── Trunk.toml Now in our Trunk.toml, we setup a hook that executes before build, so output css is compiled automatically.\n1 2 3 4 [[hooks]] stage = \u0026#34;pre_build\u0026#34; command = \u0026#34;tailwindcss\u0026#34; command_arguments = [\u0026#34;-c\u0026#34;, \u0026#34;./tailwind.config.js\u0026#34;, \u0026#34;-o\u0026#34;, \u0026#34;./public/css/out.css\u0026#34;, \u0026#34;--minify\u0026#34;] Now in your index.html (at the root of your project), link the output css file.\n1 \u0026lt;link data-trunk rel=\u0026#34;css\u0026#34; href=\u0026#34;public/css/out.css\u0026#34; /\u0026gt; Run trunk serve and you\u0026rsquo;re done! That\u0026rsquo;s it for this blog, see you soon.\n","permalink":"https://rohith.net/posts/yew-rs-with-tailwind/","summary":"Setting up Yew.rs with TailwindCSS to build a web frontend in Rust using WASM.","title":"Yew.rs With TailwindCSS"},{"content":" Hello! 11 days ago I switched from x11 to wayland, because 60 FPS scrolling is a feature too good to miss.\nNow what is wayland, you may ask? Wayland is like a replacement to the traditional x11 display protocol. You can read more about it here.\nMy x11 setup consisted of the following software:\nDWM as the window manager. Polybar as the status bar. Dunst as the notification daemon. ST as my terminal emulator. Betterlockscreen as the lockscreen. Rofi as the app launcher. Now before switching to wayland, some of these tools listed above straight up break in a wayland environment, like betterlockscreen and rofi. ST also behaves weirdly on wayland, but it works. And of course, since DWM is an x11 window manager, you cannot use it on wayland.\nChoosing a compositor Sway was an awesome choice, but I didn\u0026rsquo;t really like the i3-ish aspect of it.\nSo I was looking for something more DWM-like.\nRiver was a great replacement.\nChoosing a bar There are other alternatives like yambar, but I chose waybar.\nNotification daemon While dunst works in wayland, I opted for a notification daemon called mako instead.\nTerminal emulator ST was a great option, but I had to part ways with it. I switched to the foot terminal instead.\nLock screen Since betterlockscreen does not work on wayland, I chose swaylock for the lock screen.\nApplication launcher I use rofi-lbonn-git for the app launcher.\nMy setup You can find my dotfiles here.\nThat\u0026rsquo;s it for this blog see you soon!\n","permalink":"https://rohith.net/posts/switching-to-wayland/","summary":"Switching from X11 to Wayland and finding a Wayland replacement for each part of the old setup.","title":"Switching to Wayland"},{"content":" Introduction After using GNU Make for automating the build step in my projects, I had this idea of making my own build automation tool like Make.\nI originally wanted to use Makefile-like syntax for the config file but instead settled with TOML after thinking about it for a while. I also used Rust to write this since I am kinda familiar with the language.\nRust has a TOML crate for parsing TOML files. It also provides support for deserialization and serialization using serde.\nIt\u0026rsquo;s also extremely easy to use:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // we store custom and pre as HashMap\u0026lt;String, Struct\u0026gt; so that in the TOML file we can use dynamic table names. // for environment variables, it\u0026#39;s stored as HashMap\u0026lt;String, String\u0026gt; so we can specify as many env vars as we like. #[derive(Debug, Deserialize)] struct Recipe { build: Build, custom: Option\u0026lt;HashMap\u0026lt;String, Custom\u0026gt;\u0026gt;, // custom commands pre: Option\u0026lt;HashMap\u0026lt;String, Pre\u0026gt;\u0026gt;, // pre-build commands env: Option\u0026lt;HashMap\u0026lt;String, String\u0026gt;\u0026gt;, // for environment vars } /* \u0026lt;the build, custom and pre structs\u0026gt; */ let recipe: Recipe; match toml::from_str(\u0026amp;recipe_str) { Ok(r) =\u0026gt; recipe = r, Err(e) =\u0026gt; { printb!(\u0026#34;Error: {}\u0026#34;, e); exit(1); } } I didn\u0026rsquo;t include the other structs to make the snippet smaller. This was also the first time I used rust macros in a project, as you can see with the printb! macro. All it does is print Baker: \u0026lt;message\u0026gt; as coloured output.\n1 2 3 4 5 6 #[macro_export] macro_rules! printb { ($($arg:tt)*) =\u0026gt; { println!(\u0026#34;\\x1b[32mBaker:\\x1b[0m {}\u0026#34;, format!($($arg)*)); }; } Configuring baker The configuration file is put in the root directory of the project, just like the Makefile. If Baker is unable to find a config file (called recipe.toml), it auto generates one. Here\u0026rsquo;s an example config:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [env] BIN_NAME=\u0026#34;bake\u0026#34; COMPILER_FLAGS=\u0026#34;--release\u0026#34; INSTALL_PREFIX=\u0026#34;/usr/bin\u0026#34; [build] cmd = \u0026#34;\u0026#34;\u0026#34; cargo build $COMPILER_FLAGS \u0026amp;\u0026amp; cp -r ./target/release/$BIN_NAME ./bin/$BIN_NAME \u0026#34;\u0026#34;\u0026#34; [custom.clean] cmd = \u0026#34;cargo clean\u0026#34; run = false [pre.fmt] cmd = \u0026#34;cargo fmt\u0026#34; Baker is invoked using bake and by default checks for the build field in the recipe.toml. The cmd value is then executed during build.\nYou can set environment variables using the env field.\nYou can also add custom fields using custom. It also checks for a run value that is used to check whether the command should be run after build or not. So if run = true, then the command is run after build.\nBaker also checks for an optional pre field that contains commands which should be run before build. Like for example, if you wish to format your code or something before compiling.\nYou may have noticed a name value in the custom and pre fields ([custom.name]). This is used to identify each field, and for the custom field, the name is set as an argument when Baker is invoked.\nFor example, if I have a custom field called setup:\n1 2 3 [custom.setup] cmd = \u0026#34;mkdir -p bin \u0026amp;\u0026amp; rustup install stable \u0026amp;\u0026amp; rustup default stable\u0026#34; run = false You can run bake setup to directly execute the command inside the field.\nBaker is open source and you can find the repo here. I wrote it in like 2 days, so it may have bugs which I was not able to find, so all suggestions are welcome!\nThat\u0026rsquo;s it for this blog, see you soon!\n","permalink":"https://rohith.net/posts/writing-a-build-automation-tool/","summary":"Building a build automation tool written in Rust that uses TOML for configuration.","title":"Writing a Build Automation Tool"},{"content":" Introduction I finally finished the FEN string parsing part of my chess engine after days of procrastinating and I just wanted to share my experience with everyone.\nSo a FEN string looks like this:\n1 rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 This is the FEN string of the starting position in standard chess. It looks like random crap at first glance but this string conveys a lot of information.\nSo let\u0026rsquo;s divide it into parts.\nPieces The first part conveys information regarding piece placement. Each letter represents a piece on each rank of the board, and numbers denote empty spaces.\n1 rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR Let\u0026rsquo;s look at an example from chess.com here:\nAs you can observe lower case characters are for black pieces and upper case characters are for white pieces.\nNow, moving on to the second part.\nSide to move The second part consists of just a letter which can either be b or w.\nHere we can observe that it\u0026rsquo;s w.\n1 rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR | w | KQkq | - | 0 | 1 That means it\u0026rsquo;s white\u0026rsquo;s turn to move, and b means it\u0026rsquo;s black\u0026rsquo;s turn to move.\nCastling ability The third part contains information regarding castling ability of both sides.\nK - White can castle kingside.\nk - Black can castle kingside.\nQ - White can castle queenside.\nq - Black can castle queenside.\nIf no sides can castle, - is used like so:\n1 rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1 En passant squares The next part gives us information if any en passant squares are possible. If there are no en passant squares, - is used as demonstrated in this example. If there are any, then the co-ordinates of that square is used.\nHalf move clock and full move counter The next two parts give us information regarding the number of halfmoves and fullmoves completed. Here you can observe the half move count is 0 and the full move count is 1.\nParsing the string To track the current game status, I made a struct called GameStatus.\n1 2 3 4 5 6 7 8 pub struct GameStatus { pub pieces: Vec\u0026lt;Option\u0026lt;Piece\u0026gt;\u0026gt;, pub side_to_move: Colour, pub castling_id: [bool; 4], pub en_passant: Option\u0026lt;Vec\u0026lt;Square\u0026gt;\u0026gt;, pub half_move_clock: u32, pub full_move_count: u32, } What I wanted to do here was make functions for each variable and then assign it to the fields in the struct.\nFor parsing the side to move I first created an enumerator called Colour.\n1 2 3 4 5 pub enum Colour { White, Black, Undefined, } White and Black are both sides, and undefined is used for error handling if the input is invalid.\nI then created a function that will return a value based on the input given. I really love rust\u0026rsquo;s match syntax 😄.\n1 2 3 4 5 6 7 fn active_side(input: \u0026amp;str) -\u0026gt; Colour { match input { \u0026#34;w\u0026#34; =\u0026gt; Colour::White, \u0026#34;b\u0026#34; =\u0026gt; Colour::Black, _ =\u0026gt; Colour::Undefined, } } 1 2 3 4 if colour == Colour::Undefined { println!(\u0026#34;Invalid FEN string: Failed to parse active colour.\u0026#34;); exit(1); } For parsing castling ability I decided to make an array of bools that would contain values corresponding to the info parsed from the string.\n[true, true, true, true] if input is KQkq (all sides can castle both ways).\nIndex 0 checks if the white king can castle kingside. ie, K and so on.\nIndex 1 =\u0026gt; can white king castle queenside?\nIndex 2 =\u0026gt; can black king castle kingside?\nIndex 3 =\u0026gt; can black king castle queenside?\nSo I made a function that returns this array of bools.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 fn castling_ability(input: \u0026amp;str) -\u0026gt; [bool; 4] { if input == \u0026#34;-\u0026#34; { [false, false, false, false] } else { let mut castling_id = [false; 4]; let mut castling_id_str = input.to_string(); castling_id_str.retain(|c| c != \u0026#39;-\u0026#39;); for c in castling_id_str.chars() { match c { \u0026#39;K\u0026#39; =\u0026gt; castling_id[0] = true, \u0026#39;Q\u0026#39; =\u0026gt; castling_id[1] = true, \u0026#39;k\u0026#39; =\u0026gt; castling_id[2] = true, \u0026#39;q\u0026#39; =\u0026gt; castling_id[3] = true, _ =\u0026gt; { println!(\u0026#34;Invalid FEN string: Failed to parse castling ability.\u0026#34;); exit(1); } } } castling_id } } For parsing en passant squares the solution I came up with was rather stupid.\nSince the input can either be - or multiple squares like e4e5g6 etc., I wanted a vector that contained each square.\nSo basically,\ne4e5g6 =\u0026gt; [Square::E4, Square::E5, Square::G6]\n(Square is an enum, this change was made later. Check this blog for more info. Yes, I updated this blog :D)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 fn en_passant(input: \u0026amp;str) -\u0026gt; Option\u0026lt;Vec\u0026lt;Square\u0026gt;\u0026gt; { if input.chars().all(char::is_whitespace) | input.contains(\u0026#39;-\u0026#39;) { None } else { let chars = input.chars().collect::\u0026lt;Vec\u0026lt;char\u0026gt;\u0026gt;(); if chars.len() % 2 == 0 { let mut ep_vec = Vec::new(); let mut sq; for i in 0..chars.len() { if i % 2 == 0 { let valid_chars = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;h\u0026#39;]; let mut invalid = true; for c in valid_chars.iter() { if chars[i] == *c { invalid = false; } } if i \u0026lt; chars.len() - 1 { if !chars[i].is_alphabetic() | !chars[i + 1].is_numeric() { fen_log!(\u0026#34;Invalid FEN string: Failed to parse en passant square.\u0026#34;); exit(1); } if chars[i + 1] .to_digit(10) .expect(\u0026#34;Could not parse character to digit (en passant)\u0026#34;) \u0026gt; 8 { invalid = true; } } if invalid { fen_log!(\u0026#34;Invalid FEN string: Failed to parse en passant square.\u0026#34;); exit(1); } let file; match chars[i] { \u0026#39;a\u0026#39; =\u0026gt; file = 0, \u0026#39;b\u0026#39; =\u0026gt; file = 1, \u0026#39;c\u0026#39; =\u0026gt; file = 2, \u0026#39;d\u0026#39; =\u0026gt; file = 3, \u0026#39;e\u0026#39; =\u0026gt; file = 4, \u0026#39;f\u0026#39; =\u0026gt; file = 5, \u0026#39;g\u0026#39; =\u0026gt; file = 6, \u0026#39;h\u0026#39; =\u0026gt; file = 7, _ =\u0026gt; { fen_log!(\u0026#34;Invalid FEN string: Failed to parse en passant square.\u0026#34;); exit(1); } } let rank; match chars[i + 1] { \u0026#39;1\u0026#39; =\u0026gt; rank = 7, \u0026#39;2\u0026#39; =\u0026gt; rank = 6, \u0026#39;3\u0026#39; =\u0026gt; rank = 5, \u0026#39;4\u0026#39; =\u0026gt; rank = 4, \u0026#39;5\u0026#39; =\u0026gt; rank = 3, \u0026#39;6\u0026#39; =\u0026gt; rank = 2, \u0026#39;7\u0026#39; =\u0026gt; rank = 1, \u0026#39;8\u0026#39; =\u0026gt; rank = 0, _ =\u0026gt; { fen_log!(\u0026#34;Invalid FEN string: Failed to parse en passant square.\u0026#34;); exit(1); } } sq = rank * 8 + file; ep_vec.push(match_u32_to_sq(sq as u32)); } } Some(ep_vec) } else { fen_log!(\u0026#34;Invalid FEN string: Failed to parse en passant square.\u0026#34;); exit(1); } } } Parsing the halfmove and fullmove counts were rather easy, and I just had to return a u32 from the string input.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fn halfmove_clock(input: \u0026amp;str) -\u0026gt; u32 { let mut halfmove_clock = String::new(); for c in input.chars() { if c.is_digit(10) { halfmove_clock.push(c); } } halfmove_clock.parse::\u0026lt;u32\u0026gt;().unwrap() } fn fullmove_count(input: \u0026amp;str) -\u0026gt; u32 { let mut fullmove_clock = String::new(); for c in input.chars() { if c.is_digit(10) { fullmove_clock.push(c); } } fullmove_clock.parse::\u0026lt;u32\u0026gt;().unwrap() } However for piece placement parsing, I saw a really good implementation of it in the fen crate so I decided to just joink it 😔.\nThere\u0026rsquo;s a struct called Piece that stores the type, colour and symbol (added by me to print out pieces in the board).\n1 2 3 4 5 pub struct Piece { pub kind: Kind, pub colour: Colour, pub symbol: char, } And an enum Kind:\n1 2 3 4 5 6 7 8 pub enum Kind { King, Queen, Bishop, Knight, Rook, Pawn, } And according to the input given, a value is returned that is later pushed to a vector.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let (color, kind, symbol) = match piece_char { \u0026#39;P\u0026#39; =\u0026gt; (Colour::White, Kind::Pawn, \u0026#39;P\u0026#39;), \u0026#39;N\u0026#39; =\u0026gt; (Colour::White, Kind::Knight, \u0026#39;N\u0026#39;), \u0026#39;B\u0026#39; =\u0026gt; (Colour::White, Kind::Bishop, \u0026#39;B\u0026#39;), \u0026#39;R\u0026#39; =\u0026gt; (Colour::White, Kind::Rook, \u0026#39;R\u0026#39;), \u0026#39;Q\u0026#39; =\u0026gt; (Colour::White, Kind::Queen, \u0026#39;Q\u0026#39;), \u0026#39;K\u0026#39; =\u0026gt; (Colour::White, Kind::King, \u0026#39;K\u0026#39;), \u0026#39;p\u0026#39; =\u0026gt; (Colour::Black, Kind::Pawn, \u0026#39;p\u0026#39;), \u0026#39;n\u0026#39; =\u0026gt; (Colour::Black, Kind::Knight, \u0026#39;n\u0026#39;), \u0026#39;b\u0026#39; =\u0026gt; (Colour::Black, Kind::Bishop, \u0026#39;b\u0026#39;), \u0026#39;r\u0026#39; =\u0026gt; (Colour::Black, Kind::Rook, \u0026#39;r\u0026#39;), \u0026#39;q\u0026#39; =\u0026gt; (Colour::Black, Kind::Queen, \u0026#39;q\u0026#39;), \u0026#39;k\u0026#39; =\u0026gt; (Colour::Black, Kind::King, \u0026#39;k\u0026#39;), _ =\u0026gt; return None, }; You can check out the crate\u0026rsquo;s code for more info.\nI want to write my own implementation too but I\u0026rsquo;m too lazy and it just works.\nI also decided to add this function to print out the game state. So basically what I did was:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 pub fn print_board(game_state: \u0026amp;GameStatus) { let mut board: Vec\u0026lt;char\u0026gt; = Vec::new(); for piece in game_state.pieces { if piece == None { board.push(\u0026#39; \u0026#39;); } else { board.push(piece.as_ref().unwrap().symbol); } } let mut x = 8; println!(\u0026#34;+---+---+---+---+---+---+---+---+\u0026#34;); for rank in 0..8 { x -= 1; for file in 0..8 { let square = rank * 8 + file; if board[square] == \u0026#39; \u0026#39; { print!(\u0026#34;| \u0026#34;); } else { print!(\u0026#34;| {} \u0026#34;, board[square]); } } print!(\u0026#34;| {} \\n\u0026#34;, x + 1); println!(\u0026#34;+---+---+---+---+---+---+---+---+\u0026#34;); } println!(\u0026#34; a b c d e f g h \\n\u0026#34;); } Input: rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/PNBQKB1R b KQkq - 1 2\nOutput:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 +---+---+---+---+---+---+---+---+ | r | n | b | q | k | b | n | r | 8 +---+---+---+---+---+---+---+---+ | p | p | | p | p | p | p | p | 7 +---+---+---+---+---+---+---+---+ | | | | | | | | | 6 +---+---+---+---+---+---+---+---+ | | | p | | | | | | 5 +---+---+---+---+---+---+---+---+ | | | | | P | | | | 4 +---+---+---+---+---+---+---+---+ | | | | | | N | | | 3 +---+---+---+---+---+---+---+---+ | P | P | P | P | | P | P | P | 2 +---+---+---+---+---+---+---+---+ | P | N | B | Q | K | B | | R | 1 +---+---+---+---+---+---+---+---+ a b c d e f g h Anyways that\u0026rsquo;s it for this blog see you soon 👋.\n","permalink":"https://rohith.net/posts/fen-string-parsing-in-rust/","summary":"Breaking down a FEN string and parsing it into a usable game state for a chess engine.","title":"FEN String Parsing in Rust"},{"content":" So hi! I have been using Linux for almost 5 months now and I have learned a lot! Just wanted to share my experience with everyone.\nWhen I asked my friend (who is an expert in Linux), he recommended me Pop!_OS, an Ubuntu based distro. So, I installed it and it was my first Linux distribution. My experience using Pop!_OS was amazing and it still is one of my favourite distributions.\nWhile Linux seemed harder due to the frequent interaction with the command line, I had a bit of experience using it before (when using WSL). So the initial switch wasn\u0026rsquo;t that hard for me. I was still dual booting Windows with Pop!_OS during this time, and was still a Linux amateur.\nThings started to change when I came to know of Arch Linux, a lightweight distribution. I started reading the Arch wiki, and soon installed it on bare metal (after a bit of bullying ahem), triple booting Windows, Pop!_OS and Arch.\nAnd I ABSOLUTELY loved it. I deleted my Pop!_OS partition a while later, and Arch became my primary OS. It was a great way to learn more about Linux and its components. The first desktop environment I used with Arch, was KDE Plasma.\nWhile I certainly liked the desktop environment, I soon switched to a tiling window manager called i3. After using i3 for a few hours, I realised it wasn\u0026rsquo;t the best option for me, and switched back to KDE Plasma. A few days later I installed BSPWM, and I liked it a lot. Inspired from a friend, I installed some of his configs and modified them according to my needs.\nUsing a tiling window manager at first was a bit difficult. But soon, I started relying more on my keyboard than my mouse, switching out code editors like VS code for (Neo)Vim and relying on keyboard shortcuts for my system. Around this time, I had an idea of setting up my dotfiles repo, and I made one. Initially it was just my friend\u0026rsquo;s dotfiles, but modified according to my needs, but soon I started writing my own configs and then published the repo.\nMy BSPWM config now:\nSoon, I switched to DWM and I have been using it ever since. It\u0026rsquo;s my favourite tiling window manager. The level of extensibility it provides is just amazing! Sometimes patching DWM (as well as other suckless utilities) can be a bit tedious though.\n","permalink":"https://rohith.net/posts/my-linux-journey/","summary":"Recap of my Linux journey, from starting out on Pop!_OS to ending up on Arch Linux.","title":"A brief recap of my Linux journey up until now"}]