Justin Florentine
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
2
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# KZGamer - summoning Dankshard from the tabletop. In many tabletop games, there are often gameplay mechanics that only require a success or failure result. Boardgames, roleplaying games, and miniatures wargaming often have the notion of a "target number." If you hear phrases like "hitting on threes" or "four +es" around your table, you are experiencing one of these mechanics. This allows for a unique situation; to play the game, you only need to know if you have succeeded or failed when the dice is rolled. The actual value on the die only matters in the context of the success criteria. Once the players know if it is a success or failure, they can proceed with the game. The value that is rolled is not strictly relevant. If we had a device that could determine the result of a roll in success/failure terms, we could withold the values on the dice from the user. This gives us a source of entropy that can be used, but not revealed. The game mechanics act as a filter or obfuscation layer. Examples: * Shadowrun is a tabletop RPG set in a fantasy cyberpunk universe. All skill checks require the player to roll a number of six-sided die (D6s) and count how many were fives or sixes, which are considered hits. The players is trying to maximize the number of hits, not any particular die roll. * Bolt-Action and its Weird-War sister Konflikt '47 are miniature wargames with similar rulesets. A key part of the combat flow is determining how hard it is to hit your target. A unit may fire on another, and only score hits for dice that come up four or more, depending on conditions like range, cover, etc. In this case, again, players only need to know the quantity of successes; dice rolled which met or exceeded the target number. We hope to leverage this filter to contribute the same entropy that powered a game to the KZG Ceremony without revealing it to the players. # Potential Workflow Here's how a [dice tower](https://en.wikipedia.org/wiki/Dice_tower) could be constructed to collect the entropy from a tabletop game and only reveal the far smaller amount of information that the player needs. 1. User inputs a target condition into the dice tower. 2. D6s go into sealed dice tower. 3. Dice bounce around and land on trapdoor platform inside where they can ONLY be observed by the embedded system. 4. Embedded camera takes a picture and embedded computer reads the dice. 5. System adds new bits to the accumulated entropy. 6. System determines if those dice rolled meet target condition(s). 7. System opens trapdoor, dropping dice further through more tower and re-rolling them before returning them to the user. A dice tower like this could likely be built out of comodity embedded systems and a touchscreen display. # Potential Ceremony Interaction To contribute to the ceremony, our entropy will need to be multiplied by the previous KZG ceremony output before it is destroyed. Instead of waiting in a queue of contributors, the participation will be scheduled ahead of time and the current KZG file will be provided out of band. 1. User copies KZG input file to any removable media which is readable by the system. This is a separate storage media than the one containing the operating system, KZGamer software, and kzg contribution client. This allows the commitment medium to transit the airgap. 3. Device starts up and maintains an implementation loop of watching for dice, noticing them, reading them, and returning them to the player. 4. As entropy is accumulated, it is written to the local storage containing the software, which will never transit the airgap. This should be validated by peer review of the software, and confirmation that what is running hashes to the same thing as the reviewed code. 5. When entropy collection is complete it is passed to an existing command-line client, which then multiplies it with the previous input, and produces the next contribution output file, which is written to the same medium as the incoming KZG commitment was written to. Any interim storage of entropy can and should be deleted immediately after successful commitment. 6. A message is displayed to the user indicating the ceremony is complete and the produced KZG file should be sent back to the sequencer out of band. 7. Removable media may be destroyed. # Initial Design (and shopping list) <img src="https://i.imgur.com/8zsuAALm.jpg" style="float: right; margin-right: 25px"/> * A lightweight, portable, embedded computer system that can be air-gapped. It needs enough power for basic computer vision and the ability to run a KZG client * [Raspberry PI 4, 64 bit](https://www.raspberrypi.com/products/raspberry-pi-4-desktop-kit/) * Some kind of input system, so the user can select the target number for their rolls. * Feedback to the user, probably a display. * [7" touchscreen display](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) handles both these problems. * A camera that can read the dice. * [Raspberry PI Camera 3](https://www.raspberrypi.com/products/camera-module-3/) * A tower to house it all, a hole in the top to toss dice into, a place for them to land, a way to return the dice to the user. * Uhhh... I have a whole woodshop, I better be able to figure this out... # Construction Begins <img src="https://i.imgur.com/fjljtgom.png" style="float: right; margin-left: 25px"/> The first construction problem we encountered was making sure the camera framed the dice area correctly. The specs on the camera that estimate its field of view were close, but some trial and error was necessary to maximize how much of the camera sensor was available to look at dice. I printed out a ball and socket camera mount, which allowed me to find the optimal camera angle and distance. We wanted to maximize area covered, while avoiding views of side faces of the dice. This would later turn out to be overdesigned. <div style="clear: right"></div> --- The next problem was the actuator to return the dice to the user after the entropy had been collected. The first draft of that looked like this. {%youtube qzPVzmKyRsc %} This worked great until I started throwing lots of dice at it. The only thing holding that trapdoor level is the servo itself. Once you start dropping dice on it&mdash;which might land on either extreme from the fulcrum&mdash;you now have a decent amount of force pushing against your servo, corrupting its zero point and wearing on the gearing to the axle. This is probably the biggest problem to be addressed in the next iteration. I also experienced a couple of random restarts, likely due to powering the servo from the on-board gpio pins on the Raspberry Pi. Doing that is known to produce a pretty dirty power signal. For now we'll continue limping forward, but that issue needs to be addressed later. # Computer Vision and the D6 Big shoutout to Quentin Golsteyn whose Yahtzee project was one of the first I discovered while doing initial research to see if this whole idea was even viable. [Check out his project](https://golsteyn.com/writing/dice)! My application, however, needed to support many more than two dice, because there is nothing more satisfying than attacking your opponent with a fistful of 10 to 15 dice. It's soooo satisfying. We also need to operate in a smaller space; I am intending this dice tower to be portable so I can take it to a planned gaming event, and the area where dice land that is observed by the camera has specific needs. It must be concealed from the player and have consistent, steady lighting. It might be a little cramped in there. ## Pips <img alt = "4 and 2 read as 5 and 1" src="https://i.imgur.com/6Kh8Sw8.png" style="float: right; margin-left: 25px"/> It turns out, when dice land next to each other, the blobs-and-clusters technique Quentin developed runs into some problems. For example, the pips on a two can be closer to a neighboring die than they are to each other. Here we see one of the pips on the two was within clustering distance of the four pips on an adjacent die. It's not always the twos&mdash;but it's usually the twos. Sometimes fours. Any pips that could be closer to the pips on a neighboring die than to their own pips presents a problem. It's a pretty big problem and, at this point, I got scared; the simplicity of the blobs-and-clusters technique was doing a lot of heavy lifting! I was having no problems running this on a Raspi4 on multiple frames for each roll and returning the dice to the user quickly. I came up with two potential solutions. 1. First, maybe we introduce some edge detection to the computer vision to clearly bound the blob detection within the face of the die. That would be ideal but, as you can see from the image above, that boundary isn't always clear&mdash;you'd have to add some assumptions about boundary proportions and infer a square. Doable, but now we are straying beyond my limited Python and OpenCV capabilities which really just cover ripping off Quentin. 2. Unleash the ML. Train a model to detect what dice look like and read them. If diving deeper into traditional CV was gonna be a challenge for me, then I definitely will not have time to learn enough ML to implement it this way. Both of these options felt like staring down a deep rabbit-hole, and I still had three armies of miniatures to paint and a board full of terrain to build before gameday when the KZG commitment window is scheduled. <img src="https://i.imgur.com/w6e5DPYm.png" style="float: right; margin-left: 25px"/> So, screw it. If the problem is the dice, make better dice. Bigger dice with more distance from their edge to the pip pattern would make sure the pips always cluster within the die face. Turns out, I have a friend with a laser cutter who has experience making custom dice. He was kind enough to make me a set of custom dice on which we compressed the pip pattern toward the center of each face. Problem solved; Dankshard be praised! <div style="clear: right"></div> --- ### Interim Status Update {%youtube 7AMilMPYjgg %} *I would later discover that I was quite wrong about my assumption that dice landing on the edge of the platform is ok. It is not, and we are eventually going to have to redesign that whole mechanism.* ## Computer Vision Challenges There were a few. As you can see in the video above, there is still a lot of uncertainty around pip detection. I had heard that CV is all about controlling your environment and stripping out as much noise as possible. The rumors are true! CV issues I had to learn about and solve: * Resolution - If we don't use enough of the camera sensor at max resolution, blob detection becomes less consistent. Sensor noise in low-light conditions is enough to throw off the eccentricity detection that finds the pips. Camera position, resolution settings, and switching from video feed to still frame capture on an interval optimized for this. * Lighting - Low light tricks auto exposure settings into boosting sensor sensitivity, which introduces noise. Bright light introduces glare and reflections off of surfaces we don't care about, which often get identified as blobs. To control for this, I added dimmable LED strip lights around the camera so I could tune the lighting with a dimmer external to the case. * Contrast - Black pips on white dice are pretty ideal. How do we maximize our advantage from that? I explored a few software filtering techniques like thresholding to eliminate all the gray, but there were always reflections which ineveitably formed spheres that looked like blobs. To tame these, we painted the inside of the tower black, padded the landing deck with Gorilla Tape™ to prevent dents and paint chipping, and sealed up any light leaks from the outside. When that STILL wasn't enough, well, it was a good thing I'm really into arts and crafts. A coat of matte varnish knocked down the remaining reflections and we eventually got a nice, noiseless image. ## The Stacking Problem Yeah, we're not done with dice issues yet. But these are mechanical, not visual! So, thats fun. In rare cases, as dice tumble down the tower, they might land on top of each other. If that happened while using a dice tray, it would be obvious and everyone would go, "Whoa, neat!" and just re-roll the die that landed on top, preserving the rest of the roll. Our concealment from the user prevents that. But what we can do is give the user some feedback on how many dice were detected. Anything other than the number of dice expected would simply warrant a re-roll, and we'd go on with the game. # Trapdoor needs revision Endurance testing was not successful. If we collect two bits of entropy per die (three if we don't care about half a bit of bias), we're gonna need 512 dice tossed in there over four hours, so I would just stand there and toss dice in and watch the debug output. A smart friend questioned why I put the axle in the middle of the platform, and I didn't have a good answer. He suggested that if the axle was on one end, then you could use a latch to hold up the other end. Then you'd have a perfectly stable plane to land on and transfer no force backward through the gearing and servo. Something like this: {%youtube GT6wM5UskyQ %} When you have a 3D printer, though, it's easy to go a little crazy. I actually did a revision of the entire device which was printed in FDM and ... it wasn't great. The process of thinking in CAD is helpful, but doesn't really lend iteself to improvisation. The introduction of the solenoid was going to require more current than I could provide via the GPIO pins on the RasPi, so I added a [motor driver board](https://www.adafruit.com/product/2348) to the design. This would give us a dedicated, clean current source to drive both the solenoid and a larger servo at the expense of added software complexity. {%youtube Cn8ztnkTwO8 %} The final version would be masonite and a dowel with a small, simple geared cam on the shaft and some glue. Here is a view from the rear access panel, showing all aspects of actuation. {%youtube obb42EK7_kU %} Here you can see the resin printed cams that drive the axle and a solenoid latch in the front for the opposite edge to rest on. A few more tweaks to this basic design, and we're in business. I printed a few custom housings and brackets for the solenoid and the servo to make sure they were locked in position but could still be repaired or replaced in a mid-ceremony emergency. The last hardware issue that was causing failures over time was drifting of the camera ball joint. Vibration from dice hitting the ramp that it was mounted to eventually caused the camera position in the ball joint to shift. I tried adding a set screw to fix it in position, but the resin is just too brittle, and it loosens over time. Now that we know the proper position, though, we can replace the ball joint with a free-floating, fixed position mount. Something like this: ![](https://i.imgur.com/z5Q0d12.jpg) Now we have a fixed position, the camera is isolated from vibration, and integrated, dimmable lighting is coplanar to the camera sensor. ![](https://hackmd.io/_uploads/S1NNyFUEn.jpg) # We can no longer avoid building a UI Look, I've been a backend dev for 20+ years. My day job is working on an Ethereum client (support client diversity, run Besu!). The closest I ever got to user interfaces is website hackery back in the day. Now, this project won't need anything fancy, but we do need to accept input and display feedback to our users. I'm already using Python for the CV, the Raspberry Pi is running Raspbian so we have a full-fledged desktop. We should be able to mock up a touchscreen UI pretty easily in QT. So, lets be honest&mdash;I'm just gonna [hack this together](https://github.com/jflo/kzgamer/blob/main/MainWidget.py) with ChatGPT: ![](https://i.imgur.com/ldBNY8X.png) That's an early version of the UI running in debug mode. Still not perfect, but the image filtering is MUCH improved from where we were before. I gotta say, ChatGPT completely changed the way I'm going to learn new programming languages. It's like having a private tutor. Not only can I feed it the code and ask it how to make changes, I can ask it why things work a certain way and even ask it to make a comparison to other languages I'm more familiar with. # Dress Rehearsal Ok, I think we're ready to get this out of the lab and use this in a game. At this point, I had about a week before the convention we were playing at. A week before a four-hour window during which we would use this gadget to permanently enshrine our big game of Konflikt '47 in the foundation of Ethereum's scaling strategy. To do a proper dress rehearsal, I set up a local instance of the [KZG ceremony sequencer](https://github.com/ethereum/kzg-ceremony-sequencer) and a copy of [SCAR](https://github.com/CarlBeek/SCAR/) so that I could be absolutely sure we were executing correctly when our window opened. I went through all of the same motions that I would need to during the actual contribution window; the only difference was specifying a few alternate url options to the offline mode of the [go-kzg client](https://github.com/jsign/go-kzg-ceremony-client). Big shoutout to all the contributors of these tools! It is remarkable how little time I had to spend on the actual crypto part of this problem, leaving me more time to play with computer vision and make terrible mechanical engineering decisions. Here's what a game of Konflikt '47 looks like. It's pushing around army men and tanks on a cute little battlefield. Tape measures and dice. Pew pew! ![](https://i.imgur.com/KUV45A5.jpg) Things went very smoothly as far as device function was concerned. No misfunctions; responsive performance. After about two hours of play, however, we had only collected about 300 bits of entropy. Our goal was 1024 bits, so... we're gonna have a problem at show time. 1. Perhaps the time has come to trade bias for bandwidth. Lets collect three bits of entropy per die roll, and not worry about the fact that six is only two-and-a-half bits. 2. Can we support more types of rolls with the gadget? Right now we can only use to-hit and damage rolls versus infantry. Option one was easy to execute, but the real missed opportunity was supporting morale rolls. A morale roll in Konflikt '47 is always a roll of two dice, with the goal being to hit or get below a target number. In the beginning of the game, every unit needs to pass a morale roll to enter the battle. If they fail, they keep trying. Later in the game, as units get pinned by enemy fire, they need to make morale checks before every order they receive. If we could support morale rolls on the tower, we could add more entropy, especially at the beginning of the game. ![](https://i.imgur.com/EwsdA60.png) Fire up ChatGPT, explain [the changes](https://github.com/jflo/kzgamer/blob/26d0409cfbf4bcec706004a690a06c59a1d93ef8/MainWidget.py#L116) we want to make to the UI, and we're good to go. Now the user needs to change the mode when selecting their target number. So, we increase the range of selectable target numbers and disable any which would be invalid. With that last-minute feature added, we might be done! I'll just spend the rest of this last week painting minis, building terrain, and doing more endurance testing... # Disaster It's amazing how something as innocuous as turning the heat off overnight in your workspace can totally jeopardize weeks of work. I should just let Exhausted Past Justin explain the chain of events that struck less than 72 hours before gametime: {%youtube RiLu5t8PrK4 %} So that sucked. # Whatever, lets go to the party! We set up a camera pointed at the tower to make sure nobody tampered with it. It's a riveting four hours of dice going into a tower. I live-streamed it at the time on Twitch and also saved it to disk. The video is 19GB, though, so I'm not posting it on YouTube&mdash;but can provide it if someone is masochistic enough to want it. I explained what was going on, how this was a fun and goofy way to contribute to a shared secret that would power the future of Ethereum scaling and everyone was happy to participate. Nobody had any issues with playing a game where they could not see their die rolls. ![](https://i.imgur.com/7kvXYaV.jpg) That's the game board after everything is fully deployed. We are underway and collecting entropy! The only real question remaining is: Will we hit the full 1024 bits of entropy? ![](https://i.imgur.com/1xH6FjT.jpg) By turn three, we've already collected 415 bits of entropy, a big improvement from the dress rehearsal. Troops are still moving into position and crossing the board, so we expect the rate of entropy collection to increase a lot as infantry units engage in the fight. ![](https://i.imgur.com/gBVcAGj.jpg) And that's when the Werewolves came out. Only thing worse than Werewolves is Nazi Werewolves. ![](https://i.imgur.com/HN3Wf9R.jpg) A bolt of red, white and blue darts across the battlefield&mdash;the Star-Spangled-Man-With-A-Plan! The game was a smashing success, and 15 minutes before our contribution closed we crossed the 1024 bit mark. The tower said [**"CEASE HOSTILITIES - DANKSHARD BE PRAISED"**](https://github.com/jflo/kzgamer/blob/26d0409cfbf4bcec706004a690a06c59a1d93ef8/KZGamer.py#L137) and began the commitment process. Once complete, I recovered the commitment and sent it to the sequencer. `0xF51D203536Ea8b5BFBc06b3a1c21514766b22BB1` is the address we submitted under, you can search for it at [ceremony.ethereum.org](ceremony.ethereum.org) to confirm its inclusion. > Powers of Tau Pubkeys: **(2^12)**: `0x9157bfd6d53e6f9cd427aa84acfe47e7f1224ab99ac2c606ccea8dec3ba85a73d3b29c74422461697e9d86e36baa7f8d02add99798a40e2f32c2cd7185b13ad86f57fb7df418c708727d9ff2f85cc220f66353408151075785caf78f3b6c4dbe` **(2^13)**: `0x92ae7793b9ba21e8dcfa88e4722acd8b5282d0ad9f9407db13b966797d8a98474986e3beb0d6271fb4fe68ea485dd8ff05144addcf1af56f69a2c26ad5e6009b481e77f431247a03c22e458da0f5264e1d0db7e3e5aa6f1fc93cf539271b77fc` **(2^14)**: `0x908b21c002bb0a336e8c22da6c5f3ae688c7319f72045eff8817233fbb3c29a652f04a435e4392507462b4530f80db5c102844255ec594c1b15b3b2ae9f3a3797436846f754e8769f4c5450902d2f3d704971fb998881aeea998c64d37198391` **(2^15)**: `0xa64a105c21f2d9cff3799ef38203ae6da23b12be6949ab8dd136244de4af918b6bdb4d69537af464c3b3a156f89655720b827a331ad71d3a43ce9420b7b81be6e81b69009824de01b618ea65d2512da82215d75c99fbe9e010df5c51279416e6` Victory came down to the very last die on the very last turn. Germany barely eked out a victory, recovering the second objective it needed at the last minute! Huzzah! All that was left to do was destroy the media that KZGamer runs on. It had [already deleted](https://github.com/jflo/kzgamer/blob/26d0409cfbf4bcec706004a690a06c59a1d93ef8/KZGamer.py#L144) the entropy storage after completing the commitment, but just to be sure... {%youtube x_HAOf7N7RU %} # Epilogue Here's a post-event tour of the tower: {%youtube S5CrcqG5kGo %} I think it is super cool that the Ethereum dev community arranged such a neat opportunity and that I could turn it into a really fun event for me and my friends. Shoutout [Trent Van Epps](https://twitter.com/trent_vanepps) and [Carl Beekhuizen](https://twitter.com/CarlBeek) for managing the whole KZGCeremony. Shoutout [ Ignacio Hagopian](https://twitter.com/ignaciohagopian) who quickly responded to feedback on offline features for the go-kzg client. Shoutout [Quentin Golsteyn](https://golsteyn.com/) whose experiments in CV reading dice convinced me this stupid idea was only stupid, not crazy. Shoutout to all my degen gamer friends who played the game in testing and production, and listened to me rubber-duck debug alllllll the issues I ran into along the way. Most importantly, shoutout to my family who put up with me being isolated in the shed, grinding on this project for the weeks that it took.

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully