text:
@import "styles/base.less";
@import "styles/languages/markdown.less";
@import "styles/languages/bibtex.less";
@import "styles/languages/js.less";
@import "styles/languages/css.less";
@import "styles/languages/python.less";
@import "styles/languages/vala.less";
@import "styles/languages/json.less";

repo_name: nylki/pen-paper-coffee-syntax

repo_language: Less


text:
# Pen Paper Coffee :pencil2::scroll::coffee:
[![](https://badgen.net/apm/downloads/pen-paper-coffee-syntax)](https://atom.io/themes/pen-paper-coffee-syntax) [![](https://badgen.net/apm/stars/pen-paper-coffee-syntax)](https://atom.io/themes/pen-paper-coffee-syntax)

*Pen Paper Coffee* is a syntax theme for the text editor [atom](http://atom.io/), which is designed specifically for writing papers, essays etc. in [markdown](https://en.wikipedia.org/wiki/Markdown).
The theme features ~~increased font-sizes for each level of headers~~ (see *changes* on the bottom), several useful, but not distracting syntax highlightings.
The overall colors are inspired by the soft beige paper you would find in a notebook. While the ink (text color) is a dark blue, all quotes and citations are in a warm brownish orange to contrast them better to your own words. When writing text, not code, I usually set a line height of 1.4 or 1.5 in atoms settings, as you can see in the sample screenshots.

Although the goal of this theme was to have a pleasant markdown writing experience, it looks good with other docs too! I personally use it for most of my coding work (JS, HTML, CSS, c++, glsl).

The screenshots without gutter and text centering had [Zen](https://atom.io/packages/zen) installed, if you like to have centered text but still see the gutter, treeview and tabs [typewriter](https://atom.io/packages/typewriter) might be for you.

When you want to cite stuff from eg. a bibtex file, I find a great package to use together with this theme is [autocomplete bibtex](https://github.com/apcshields/autocomplete-bibtex). It's a great help when combining it with [pandoc](https://github.com/jgm/pandoc) for pdf & latex export from markdown. Work great with the [language-pfm](https://github.com/leipert/language-pfm) package.

## VS Code port
If you are looking for Visual Studio Code version of this theme, take a look: https://github.com/nylki/pen-paper-coffee-vscode

### screenshots

![markdown with zen mode](https://raw.githubusercontent.com/nylki/PenPaperCoffee-atom-syntax/master/screenshots/penPaperCoffe_1.png)

support for critic markup:
![critic markup](https://raw.githubusercontent.com/nylki/PenPaperCoffee-atom-syntax/master/screenshots/criticmarkup.png)

![css screenshot](https://raw.githubusercontent.com/nylki/PenPaperCoffee-atom-syntax/master/screenshots/penPaperCoffe_4.png)

![A screenshot of your theme](https://raw.githubusercontent.com/nylki/PenPaperCoffee-atom-syntax/master/screenshots/penPaperCoffe_2.png)

![A screenshot of your theme](https://raw.githubusercontent.com/nylki/PenPaperCoffee-atom-syntax/master/screenshots/penPaperCoffe_3.png)

## changes
### 2. October 2015

Due to rendering issues in Atom, I reduced the increased fonts for headings. It made too many problems unfortunately :/
If the issues are resolved I would love to get the increased heading sizes back! (you still can get the *old* version at: https://github.com/nylki/pen-paper-coffee-syntax/releases/tag/v0.9.1 if you don't mind the strange cursor positionings and the problems listed in the issue below)
Relevant issues:

- [https://github.com/nylki/pen-paper-coffee-syntax/issues/20#issuecomment-144845830](https://github.com/nylki/pen-paper-coffee-syntax/issues/20#issuecomment-144845830)

- [https://github.com/atom/atom/issues/6055](https://github.com/atom/atom/issues/6055)

### 11. April 2015
I changed the gutter to a lighter brown, which is much more pleasing, atleast to my eyes. If you prefer the darker gutter, just comment out and uncomment the following lines in `colors.less` to your liking:
```.less
// gutter colors
@heftstreifen: rgb(224, 218, 211);

//@heftstreifen: rgb(202, 193, 186); //old darker gutter
```

mime_type: text/plain


text:
# Random Article
Far far away, behind the word mountains, far from the countries *Vokalia* and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the codeast of the *Semantics*, a large language ocean.
And here is a [link](https://github.com/nylki/PenPaperCoffee-atom-syntax)
just for you. Now lets go over to some quotes and citations:

## On Rivers of Words
### First part

A small river named Duden flows by their place and supplies it with the necessary *regelialia*. It is a paradisematic country, in which roasted parts of sentences fly into your mouth [see @randomCitation2014].
Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
As somebody said in the book:

> "But nothing the copy said could convince her and so it didn’t take long  until a few insidious *Copy Writers* ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn’t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts."^[@test This is a footnote, p.22]

### A heading
She packed her seven *versalia*, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown *Bookmarksgrove*, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. Another quote:

> "The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild *Question Marks* and devious Semikoli, but the Little Blind Text didn’t listen."

#### Important paragraph
Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A bold **river** named Duden flows by their place and supplies it with the necessary regelialia. ~~Here is some stroked text.~~
The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word "and" and the Little Blind Text should turn around and return to its own, safe country.

#### Unimportant Paragraph
It is a *paradisematic country*, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.

She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the **first hills** of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word "and" and the Little Blind Text should turn around and return to its own, safe country.^[This is not so cool]

But nothing the copy said could convince her and so it didn’t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn’t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth.^[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.]

Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life **One** day however a small line of blind text by the
name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way.

- [ ] asdasd
- [ ] asdas
- [x] adasd

On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word "and" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn’t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn’t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts.

Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word "and" and the Little Blind Text should turn around

## List test

- This is a list item
- this too
    - Subitem
    - antother subitem
- bla

### Checklist

- [x] Color some text
- [ ] write some text

## critic markup tests

Don't go around saying{-- to people that--} the world owes you a living. The world owes you nothing. It was here first. {~~One ~> Only one~~} thing is impossible for God: To find {++any++} sense in any copyright law on the planet. {==Truth is stranger than fiction==}{>>strange but true<<}, but it is because Fiction is obliged to stick to possibilities; Truth isn’t.

## pandoc tests

The following tests should work if you use the atom [language-pfm](https://atom.io/packages/language-pfm)

### code

```.js
let foo = function bla() {

}
```

### Math

**The following should work**

Inline math should look really nice, whether with `$...$` $x \subset y$ or `\(...\)` \(x \cap y \)

But also as math blocks with `$$...$$` or `\[...\`:

$$
x \subset y\\
x = y
$$

$$
E = mc^2
$$

**The following are not math**

There should be no space between the dollar sign and the inline-math: $1000; $x \subset y $

### Inline HTML

**Markdown works in inline html**

~~Markdown works in inline html~~

### Definition Lists

Long definition with `:` syntax:

bar
:   Is a short definition

~~foobar~~
:   Is a mixture of

    1. foo
    ```
    var x = y;
    a
    ```
    2. **bar**

Short definition with `~` syntax:

Term 2
  ~ Definition 2a

    Definition 2b

Definitions ends before that line

# unwanted behaviour
[p. 1, This is *not* a list. **bold text**]
^[p. 1, This is *not* a list **bold text**]

- asdasd


text:
// These colors are specific to the theme. Do not use in a package!

@cursor-color: rgb(30, 30, 8);

@comment-ink: rgb(156, 163, 176);
@invisible-ink: rgba(169, 193, 214, 0.70);
@very-light-ink :rgb(110, 125, 170);
@light-ink:      rgb(37, 95, 147);
@ink:            rgb(43, 63, 106);
@dark-ink:       rgb(42, 18, 10);

@heading-ink: @dark-ink;
@quote-color:         fade(rgb(126, 81, 33),77%);
@quote-support-color: @very-light-ink;

@citation-symbol-color: rgba(169, 86, 31, 0.43);
@citation-name-color:   rgb(170, 92, 8);

//old @emphasize-color:   rgb(8, 123, 172);
@emphasize-color: spin(saturate(@light-ink, 25%), -7);
@emphasize-quote-color: spin(saturate(@quote-color, 40%), -10);

@bold-color:        darken(@emphasize-color, 10%);
@bold-quote-color:  @quote-color;

@paperwhite:  rgb(248, 246, 241);
@text-selection-color: darken(@paperwhite, 12%);
@cursor-line-color: fade(@text-selection-color, 20%);

// gutter colors
@heftstreifen: rgb(224, 218, 211);

//@heftstreifen: rgb(202, 193, 186); //old darker gutter

@heftstreifen-selected: darken(@heftstreifen, 12%);

@list-color:    #d48767;
@cyan:          #379566;
@blue:          #5e78e1;
@purple:        #8100c6;
@green:         #8f932b;
@red:           #c16161;
@orange:        #d08e25;
@light-orange:  #e3a92d;

// git colors
@git-removed: rgb(215, 56, 56);
@git-added: rgb(9, 192, 3);
@git-modified: rgb(255, 199, 34);
@git-renamed: rgb(53, 168, 233);

 // alternative colors
/*
@cyan:         #35a766;
@blue:         #5977d3;
@purple:       #8100c6;
@green:        #8fad06;
@red:          #d24d4d;
@orange:       #cd8e21;
@light-orange: #e3a92d;
*/


text:
@import "syntax-variables";

atom-text-editor {
  background-color: @syntax-background-color;
  color: @syntax-text-color;

  .line.cursor-line {
    background-color: @syntax-cursor-line;
  }

  .wrap-guide {
    background-color: @syntax-wrap-guide-color;
  }

  .indent-guide {
    color: @syntax-indent-guide-color;
  }

  .invisible-character {
    color: @invisible-ink;
  }

  .gutter-container {
    // add a small margin right to the gutter
    margin-right: 0.8em;
  }

    .gutter {
      background-color: @syntax-gutter-background-color;
      color: @syntax-gutter-text-color;

      .line-number {
        -webkit-font-smoothing: antialiased;
        &.cursor-line {
          background-color: @syntax-gutter-background-color-selected;
          color: @syntax-gutter-text-color-selected;
        }
        &.cursor-line-no-selection {
          color: @syntax-gutter-text-color-selected;
        }
      }
    }

    .line-number.folded, .line-number:after, .fold-marker:after {
      color: @light-ink;
    }

  .selection .region {
    background-color: @syntax-selection-color;
  }
} // .editor bracket close

.bracket-matcher .region{
  background-color: rgba(237, 169, 251, 0.61);
}

.syntax--comment {
  color: @comment-ink;
  font-style: italic;
}

.syntax--entity {
  &.syntax--name.syntax--type {
    color: @cyan;
  }

  &.syntax--other.syntax--inherited-class {
    color: @green;
  }
}

.syntax--keyword {
  color: @green;

  &.syntax--control {
    color: @ink;
    font-weight: bold; // slightly bolder than normal
  }

  &.syntax--operator {
    font-weight: bold;
    color: darken(@cyan, 20%);
  }

  &.syntax--other.syntax--special-method {
    color: @blue;
  }

  &.syntax--other.syntax--attribute-name {
  color: @ink;
  font-weight: bold;
  }

  &.syntax--other.syntax--unit {
    color: @green;
  }
}

.syntax--storage {
  color: @blue;
  &.syntax--todo, &.syntax--idea, &.syntax--fixme, &.syntax--hack {
    color: @purple;
    background-color: fade(@light-orange, 10%);
    text-decoration: none;
    border-bottom: @invisible-ink;
    border-width: 0px 0px 1px 0px;
    border-style: none none dotted none;
  }
}

.syntax--constant {
  color: @red;

  &.syntax--character.syntax--escape {
    color: @green;
  }

  &.syntax--numeric {
    color: @red;
  }

  &.syntax--other.syntax--color {
    color: @green;
  }

  &.syntax--other.syntax--symbol {
    color: @green;
  }
}

.syntax--variable {
  color: @blue;

  &.syntax--interpolation {
    color: darken(@blue, 10%);
  }

  &.syntax--parameter.syntax--function {

  }
}

.syntax--invalid.syntax--illegal {
  background-color: @red;
  color: @syntax-background-color;
}

.syntax--string {

&.syntax--interpolated{
  background-color: rgba(255, 204, 0, 0.03);
}

&.syntax--quoted{

    background-color: rgba(255, 204, 0, 0.03);
    color: darken(@quote-color, 5%);
}

  &.syntax--regexp {
    color: @cyan;

    .syntax--source.syntax--ruby.syntax--embedded {
      color: @orange;
    }
  }

  &.syntax--other.syntax--link {
    color: @red;
  }
}

.syntax--punctuation {
  &.syntax--definition {
    &.syntax--comment {
      color: darken(@comment-ink,10%);
    }

    &.syntax--tag{
      color: darken(@red, 10%);
    }

    &.syntax--string,
    &.syntax--variable,
    &.syntax--array {

    }

      &.syntax--parameters, &.syntax--arguments {
        &.syntax--end, &.syntax--begin
        {
          color: @very-light-ink;
        }
      }

    &.syntax--heading,
    &.syntax--identity {
      color: @blue;
    }

    &.syntax--bold {
      color: bold-color;
      font-weight: bold;
    }
  }

  &.syntax--section.syntax--embedded {
    color: darken(@red, 10%);
  }

}

.syntax--support {
  &.syntax--class {
    color: @cyan;
   //color: @very-light-ink;
  }

  &.syntax--function  {

    color:@orange;

  }
}

.syntax--entity {
  &.syntax--name.syntax--function {
    color: @orange;
  }

  &.syntax--name.syntax--class, &.syntax--name.syntax--type.syntax--class {
    color: @ink;
  }

  &.syntax--name.syntax--section {
    color: @red;
  }

  &.syntax--name.syntax--tag {
    color: @blue;
  }

  &.syntax--name.syntax--label {
    color: @blue;
  }

  &.syntax--other.syntax--attribute-name {
    color: @orange;

    &.syntax--id {
      color: @blue;
    }
  }
}

.syntax--meta {
  &.syntax--class {
    color: @ink;
  }

  &.syntax--brace{

  }

  &.syntax--method{
  color:@light-ink;
  }

  &.syntax--link {
    color: @orange;
  }

  &.syntax--require {
    color: @blue;
  }

  &.syntax--selector {
    color: @orange;
  }

  &.syntax--separator {
    color: @syntax-text-color;
  }
}

.syntax--none {
  color: red;
}

//create space for H1 at top of document
// FIXME: this is gone for now, somehow also displaches autocomplete popups
// will evaluate, once increased font-sizes are working reasonable

// atom-text-editor[data-grammar="source gfm"]:not(.mini),
// atom-text-editor[data-grammar="source gfm"]:not(.mini)::shadow{
//     .gutter,
//     .scroll-view {
//       padding-top: 0.75em;
//       .line-number-0 {
//         padding-top: 0.75em;
//         top: -0.75em !important; //overrule inline style
//       }
//
//   }
// }

/*---------------------------------------
 below: optional extra stuff for addon packages
-----------------------------------------*/

//highlight-selected
.highlights {
.highlight-selected .region {
border-radius: 4px;
box-sizing: content-box;
padding: 0 1px 0 0;
margin: -1px 0 0 -1px;
background-color: transparent;
border-width: 1.5px;
border-style: dashed;
}

// Light theme box (set in settings)
.highlight-selected.light-theme .region {
border-color: rgba(238, 11, 143, 0.8);

}
// Light theme background (set in settings)
.highlight-selected.light-theme.syntax--background .region {
background-color: rgba(236, 13, 132, 0.35);
}
}

// add a bigger margin right to the gutter if markdown
// for regular margin for other files, see above, where general
// gutter colors are defined
atom-text-editor[data-grammar="source gfm"] .gutter-container {
    margin-right:2em;
}


text:
@import "colors";

// This defines all syntax variables that syntax themes must implement when they
// include a syntax-variables.less file.

// General colors
@syntax-text-color: @ink;
@syntax-cursor-color: @cursor-color;
@syntax-selection-color: @text-selection-color;
@syntax-background-color: @paperwhite;
@syntax-cursor-line: @cursor-line-color;

// Guide colors
@syntax-wrap-guide-color: @heftstreifen;
@syntax-indent-guide-color: @invisible-ink;
@syntax-invisible-character-color: @ink;

// For find and replace markers
@syntax-result-marker-color: @light-ink;
@syntax-result-marker-color-selected: white;

// Gutter colors
@syntax-gutter-text-color: @dark-ink;
@syntax-gutter-text-color-selected: @syntax-gutter-text-color;
@syntax-gutter-background-color: @heftstreifen;
@syntax-gutter-background-color-selected: @heftstreifen-selected;

// For git diff info. i.e. in the gutter
@syntax-color-renamed: @git-renamed;
@syntax-color-added: @git-added;
@syntax-color-modified: @git-modified;
@syntax-color-removed: @git-removed;


text:
.syntax--source .syntax--js {
 &.syntax--brace.syntax--round, &.syntax--brace.syntax--curly {
     color: darken(@very-light-ink, 5%);
 }

 &.syntax--punctuation.syntax--terminator.syntax--statement {
      color: @light-ink;
}

&.syntax--delimiter.syntax--method.syntax--period {
  color: @very-light-ink;
}

&.syntax--delimiter.syntax--object.syntax--comma {
  color: darken(@very-light-ink, 5%);
}

// `this` keyword in JS
&.syntax--variable.syntax--language.syntax--js {
  color: @cyan;
}

}


text:
.syntax--markup, .syntax--md, .syntax--gfm, .syntax--pfm {

  &.syntax--changed {
    color: @purple;
  }
  &.syntax--deleted {
    color: @red;
  }
  &.syntax--italic {
    color: @emphasize-color;
    font-style: italic;
    .syntax--punctuation {
      opacity: 0.6;
    }
  }
  &.syntax--bold,
  &.syntax--heading,
  &.syntax--strong.syntax--emphasis {
    color: @bold-color;
    font-weight: bold;
    .syntax--punctuation {
      opacity: 0.5;
    }
  }
  &.syntax--quote {
    color: @quote-color;
    font-style: italic;
    // italics inside quotes/blockquotes
    .syntax--italic{

      color: @emphasize-quote-color;
    }
    .syntax--bold,.syntax--strong{
      color: @bold-quote-color;
      font-weight: bold;
    }
  }

  &.syntax--heading .syntax--punctuation.syntax--definition.syntax--heading {
    color: @blue;
  }
  &.syntax--inserted {
    color: @green;
  }
  &.syntax--list {
    & > .syntax--punctuation {
      color: @list-color;
      font-weight: bold;
    }
  }
  &.syntax--raw, &.syntax--embedded.syntax--source.syntax--js, &.syntax--fenced.syntax--code {
    background-color: rgba(245, 250, 252, 0.65);
    font-style: italic;

    &.syntax--inline {
      color: @green;
    }
  }
  &.syntax--strike {
    text-decoration: line-through;
  }
  &.syntax--underline {
    text-decoration: none;
    border-bottom: @invisible-ink;
    border-width: 0px 0px 1px 0px;
    border-style: none none solid none;
  }
  &.syntax--heading {
    color: @heading-ink;
    font-weight: normal;
    // position: relative;
    &.syntax--heading-1 {
      font-weight: 900;
      // font-size: 105%;
      // font-size: 135%;
      // bottom: 0.35em;
      opacity: 1.0;
    }
    &.syntax--heading-2 {
      font-weight: 800;
      // font-size: 102%;
      // font-size: 125%;
      // bottom: 0.25em;
      // opacity: 0.95;
      opacity: 0.9;
    }
    &.syntax--heading-3 {
      font-weight: 700;
      // font-size: 115%;
      // bottom: 0.15em;
      // opacity: 0.90;
      opacity: 0.85;
    }
    &.syntax--heading-4,
    &.syntax--heading-5,
    &.syntax--heading-6 {
      // font-size: 105%;
      // bottom: 0.05em;
      // opacity: 0.80;
      opacity: 0.80;
    }
    // marker of headings
    &.syntax--marker {
      color: fade(@heading-ink, 55%);
    }
  }

  &.syntax--support {
    color:@very-light-ink;
  }

  &.syntax--markup.syntax--heading {
    font-weight: normal;
  }
  &.syntax--variable {
    color: @blue;
  }
  &.syntax--entity {
    color: @light-ink;
  }
  &.syntax--punctuation.syntax--definition {
    &.syntax--begin, &.syntax--end {
      color: @very-light-ink;
    }
  }
  // Citations, like: [@citekey]
  &.syntax--username, &.syntax--reference.syntax--variable {
    // name after @ in citation
    color:  @citation-name-color;
  }
  // language-markdown has .punctutation as child. pfm .mention as sibling
  &.syntax--mention, &.syntax--reference.syntax--variable > .syntax--punctuation {
    // @ symbol before citation
    color: @citation-symbol-color;
  }

  // all kind of lists
  // variable.list is for pfm/gfm
  &.syntax--list {

    &.syntax--variable {
      color: @list-color;
      font-weight: bold;
    }
    > .syntax--punctuation {
      font-weight: bold;
      opacity: 1;
      color: @list-color;

      .syntax--checkbox {
        color: @list-color;
        background: fade(@list-color, 5%);
      }
    }

    &.syntax--unordered,
    &.syntax--ordered {}

      &.syntax--task {
        &.syntax--completed {
          color: @ink;
          opacity: 0.75;

          .syntax--code {
            background: transparent;
            color: @ink;
          }

          .syntax--todo,
          .syntax--review {
            color: inherit;
          }

          .syntax--fixme {
            background: transparent;
            outline: none;
            color: inherit;
          }
        }
      }

      &.syntax--empty {
        .syntax--punctuation {
          color: @list-color;
        }
      }
    }

    &.syntax--critic {

      &.syntax--addition {
        color: @green;
        &.syntax--marker {
          color: fade(@green, 50%) !important;
        }
      }

      &.syntax--deletion {
        color:  @red;
      }
      .syntax--deletion.syntax--marker {
        color: fade(@red, 50%) !important;
      }

      &.syntax--substitution {
        color:   @orange;
        &.syntax--marker {
          color: fade(@orange, 50%) !important;
        }
        &.syntax--operator {
          color: darken(@orange, 10%) !important;
        }
      }

      &.syntax--comment {
        color: @comment-ink;
        &.syntax--marker {
          color: fade(@comment-ink, 50%) !important;
        }
      }

      &.syntax--highlight {
        color: @blue;
        &.syntax--marker {
          color: fade(@blue, 50%) !important;
        }
      }

    }
    &.syntax--math {
      .syntax--support {
        color: darken(@green, 10%);
      }

      .syntax--punctuation.syntax--arguments{
        color: lighten(@red, 5%);
      }

      .syntax--tex {
        &.syntax--constant {
          color: @orange;
          &.syntax--numeric {
            color: @green;
          }
        }
      }

      background: fadeout(@green,90%);
      color: @green;
    }

    &.syntax--definition.syntax--block {
      background: fadeout(@blue,90%);
    }

    &.syntax--footnote, &.syntax--link, &.syntax--auto {
      &.syntax--destination {
        .syntax--uri {
          // Same as general .underline for markups
          text-decoration: none;
          border-bottom: @invisible-ink;
          border-width: 0px 0px 1px 0px;
          border-style: none none solid none;
        }
      }

      // for now disabled as it is not reliable yet
      // position: relative;
      // top: -0.5em;
      // font-size: 85%;
      // font-weight:900;
      // so far comes from language-pfm

      // would be better?
      // font-style: normal;
      // vertical-align: super;
      // font-size: 85%;
      color:@light-ink;
    }
  }


text:
.syntax--css{
    .syntax--punctuation.syntax--separator.syntax--key-value {
        color: @light-ink;
    }
}


text:
.syntax--body.syntax--vala {
	color: @ink;
}


text:
.syntax--source.syntax--json {
  .syntax--meta.syntax--structure.syntax--dictionary.syntax--json {
    & > .syntax--string.syntax--quoted.syntax--json {
      & > .syntax--punctuation.syntax--string {
        // "" etc.
         background-color: rgba(182, 137, 215, 0);
         opacity: 0.75;
      }
      // key
       background-color: rgba(182, 137, 215, 0);
      color: @quote-support-color;
    }
  }

  .syntax--meta.syntax--structure.syntax--dictionary.syntax--json, .syntax--meta.syntax--structure.syntax--array.syntax--json {
    & > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
    & > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json > .syntax--punctuation {
      // value
      background-color: rgba(255, 204, 0, 0.0);
      color: @quote-color;
    }

    & > .syntax--constant.syntax--language.syntax--json {
      // numbers / bools
      color: @red;
      background-color: rgba(182, 137, 215, 0);

    }
  }
}


text:
.syntax--text.syntax--bibtex {
    .syntax--entry-key  {
        color: @citation-name-color;
        font-weight: bold;
    }

    .syntax--begin, .syntax--end {
        color: @ink;
    }

    .syntax--punctuation.syntax--definition.syntax--keyword {
        opacity: 0.7;
    }
}


text:
.syntax--python{
    .syntax--leading-whitespace.syntax--indent-guide  {
        // IDEA:
        //background-color: fade(@invisible-ink, 10%);
    }
}


text:
## boot up / shared generation code for all collection samples

require 'pixelart'
require 'csvreader'

def read_csv( path )
  CsvHash.read( path )
end

## check: find a better name? why? why not?
##    rename Art to Machine, Artmachine, Readymades,
##           Blocks? Collection?
class Art
  def initialize( dir: '.', mapping: {}, qualifier: {} )
    @dir = dir

    ## note: (auto-)normalize passed in from names for mappings for convenience
    ##      e.g.  Male II  => maleii  and so on
    @mapping = mapping.reduce( {} ) do |h, (from, to)|
                 h[ normalize( from ) ] = to
                 h
               end

    ## pp @mapping

    ## reorg qualifer structure internally
    ##  reverse for lookup by normalized basename
    ##   e.g.    { 'f' => ['Female I', 'Female II'],
    ##             'm' => ['Male I', 'Male II'] }
    ##              to
    ##           { 'Female I'  => 'f',
    ##             'Female II' => 'f',
    ##             'Male I'    => 'm',
    ##             'Male II'   => 'm' }
    @qualifier = qualifier.reduce( {} ) do |h, (q, keys)|
                  keys.each do |key|
                    norm = normalize( key )
                    norm = @mapping[ norm ] || norm   ## allow original names too
                    h[ norm ] = q
                  end
                  h
                end

     ##pp @qualifier
  end

  def generate( attributes )
    pp attributes

    img       = nil
    qualifier = nil      ## e.g. m/f - switch / qualifier depending on archetype (base)

    attributes.each_with_index do |(_,key), col|
       next if key.nil? || key.empty?  ## note: skip nil or empty string attributes

       basename =  normalize( key )

       ## check for known (re)mapping
       basename =  @mapping[ basename ] || basename

       path =  if qualifier && col != 0
                 "#{@dir}/#{qualifier}/#{basename}.png"
               else
                 "#{@dir}/#{basename}.png"
               end
       puts "  reading >#{path}<..."

       part = Image.read( path )
       puts "     #{part.width}x#{part.height}"

       if img   ## compose
          ## hack for doge hats - cut off / clean top (ears)
          ##   todo/fix: make "postprocessing" configurable / generic / reusable for all!!!!
          if @dir.index( 'dodge' ) && ['beanie',
                                       'cap',
                                       'forwardcap',
                                       'bandana',
                                       'cowboyhat',
                                       'fedora',
                                       'tophat',
                                       'knittedcap'].include?( basename )
             puts "  apply dodge hat hack..."
             ## "wipe-out" pixel lines 3,4,5 (top)
             [3,4,5].each do |y|
               img.width.times do |x|
                  img[ x, y ] = Color::TRANSPARENT
               end
             end
          end

         img.compose!( part )
       else     ## first layer; use a base
         img       = part

         qualifier = @qualifier[ basename ]   ## note: (optional) qualifier only works for base (first layer) for now
         if @qualifier.size > 0 && qualifier.nil?    ## note: add strict assert for required qualifier for now - why? why not?
            puts "!! ERROR - no qualifier defined for #{key} => #{basename}, sorry - please fix:"
            pp @qualifier
            exit 1
         end
       end
    end

    img
  end # method generate

  ######
  # helpers
  def normalize( str )
    ## allow (ignore) space ( ), underscore (_), dash (-), plus (+) and paras (())
    str.downcase.gsub( /[ _()+-]/, '' ).strip
  end

end   # class Art

repo_language: Ruby

mime_type: text/x-ruby


text:
Note: See [**/punks.whitelabel**](https://github.com/cryptopunksnotdead/punks.whitelabel) -
The Free White Label Punk Quick Starter Edition - if you want to (re)create from zero / scratch a pixel-perfect copy of the Matt & John's® 10 000 punks collection (anno 2017).

---

# Pixel Art Collection Quick Starter - Generate Algorithmically Your Own Curated (or Randomized) Collection

Table of Contents / Sample Collections

[**Collection №1 -  Twenty-Five Dodge Pixel Art Punks**](#collection-1----twenty-five-dodge-pixel-art-punks)
 ![](i/no1/punks.png)

[**Collection №2 -  The First One Hundred Pixel Art Punks**](#collection-2----the-first-one-hundred-pixel-art-punks)
 ![](i/no2/punks.png)

[**Collection №3 -  The First One Hundred Pixel Art Punks - The Black & White (Pencil Drawing) Sketch Edition**](#collection-3----the-first-one-hundred-pixel-art-punks---the-black--white-pencil-drawing-sketch-edition)
 ![](i/no3/punks.png)

[**Collection №4 - 111 Cool Blue Aliens (Incl. Females & Super-Rare Apes)**](#collection-4---111-cool-blue-aliens-incl-females--super-rare-apes)
 ![](i/no4/punks.png)

---

Let's start and generate algorithmically a pixel art collection in three steps:

Step 1 - Generate A List of Your Pixel Art Collection in a Tabular Text Format (Spreadsheet)

Step 2 - Generate All Pixel Art Images in Original Format and In 20x

Step 3 - Bonus: Generate an All-In-One Composite Image

## Collection №1 -  Twenty-Five Dodge Pixel Art Punks

Let's start with an exclusive twenty-five Shiba Inu dogs
pixel art collection. Much wow. Super rare.

Let's copy and (re)use all punk (building) blocks
in the dodge series (24×24):

Classic ![](dodge/classic.png),
Dark ![](dodge/dark.png),
Zombie ![](dodge/zombie.png),
Alien ![](dodge/alien.png)

Attributes (by category and a-z):

- Hat  -
  Bandana ![](dodge/bandana.png),
  Beanie ![](dodge/beanie.png),
  Cap  ![](dodge/cap.png),
  Cap Forward ![](dodge/capforward.png),
  Cowboy Hat ![](dodge/cowboyhat.png),
  Fedora ![](dodge/fedora.png),
  Headband ![](dodge/headband.png),
  Knitted Cap ![](dodge/knittedcap.png),
  Tiara ![](dodge/tiara.png),
  Top Hat ![](dodge/tophat.png)
- Hair -
  Crazy Hair ![](dodge/crazyhair.png)
- Eyes -
  3D Glasses ![](dodge/3dglasses.png),
  Big Shades  ![](dodge/bigshades.png),
  Classic Shades ![](dodge/classicshades.png),
  Eye Patch   ![](dodge/eyepatch.png),
  Nerd Glasses  ![](dodge/nerdglasses.png),
  Regular Shades  ![](dodge/regularshades.png),
  Small Shades  ![](dodge/smallshades.png)

(Source: [Punk (Building) Blocks - Dodge Series (24×24)](https://github.com/cryptopunksnotdead/punks.blocks#dodge-series-2424))

### Step 1 -  Generate A List of Your Pixel Art Collection in Tabular Text Format (Spreadsheet)

In the first sample let's generate the list of the pixel art collection
by hand. Let's use a rarity & popularity distribution of
10 classic Shibas, 7 darkies, 5 zombies and 3 aliens.  Example  -  [`dodge.csv`](no1/dodge.csv):

``` csv
type,       attribute1,   attribute2
Classic,    3D Glasses
Classic,    Beanie
Classic,    Cap,          Regular Shades
Classic,    Cowboy Hat
Classic,    Eye Patch
Classic,    Knitted Cap
Classic,    Nerd Glasses
Classic,    Big Shades
Classic,    Knitted Cap,  Regular Shades
Classic
Dark,       Tiara
Dark,       Big Shades
Dark,       3D Glasses
Dark,       Classic Shades
Dark,       Cap
Dark,       Cap,          Regular Shades
Dark,       Tiara,        Big Shades
Zombie,     Knitted Cap
Zombie,     Eye Patch
Zombie,     Regular Shades
Zombie,     Knitted Cap,  Regular Shades
Zombie
Alien,      Cap,          Small Shades
Alien,      Headband
Alien,      Classic Shades
```

Yes, that's it.
For the list format let's use the comma-separated values (.CSV) format supported by all spreadsheet programs.
Every line is a record of a punk
made up of the original type  and one or more attributes.

Note: Let's NOT add the index number (0,1,2,3, and so on).
Why? That way you can easily
reshuffle your collection anytime.
Let's try a more "random" look by hand:

``` csv
type,       attribute1,   attribute2
Classic,    3D Glasses
Dark,       Tiara
Classic,    Beanie
Zombie,     Knitted Cap
Alien,      Cap,          Small Shades
Dark,       Big Shades
Zombie,     Eye Patch
Classic,    Cap,          Regular Shades
Classic,    Cowboy Hat
Classic,    Eye Patch
Alien,      Headband
Classic,    Knitted Cap
Dark,       3D Glasses
Zombie,     Regular Shades
Classic,    Nerd Glasses
Classic,    Big Shades
Dark,       Cap
Zombie
Classic,    Knitted Cap,  Regular Shades
Classic
Dark,       Classic Shades
Dark,       Cap,          Regular Shades
Alien,      Classic Shades
Dark,       Tiara,        Big Shades
Zombie,     Knitted Cap,  Regular Shades
```

### Step 2  - Generate All Pixel Art Images in Original Format and In 20x

Now the "magic" let's read in the list
in the comma-separated values (.CSV) format
and generate all pixel art images in the original format
(24×24) and in 20x (480×480).

``` ruby
require './boot'

recs = read_csv( './no1/dodge.csv' )
puts "  #{recs.size} punk(s)"
#=>  25 punk(s)

art = Art.new( dir: './dodge' )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk.save( "./o/no1/#{name}.png" )
  punk.zoom(20).save( "./o/no1/#{name}@20x.png" )
end
```

Yes, that's it.
Run the script to generate algorithmically your collection.

In the `/o` directory you will now find
two images per punk - in the orginal format, that is, 24×24
and 20x, that is, 480×480 - and get:

```
no1/
  punk0.png
  punk0@20x.png
  punk1.png
  punk1@20x.png
  punk2.png
  punk2@20x.png
  punk3.png
  punk3@20x.png
  punk4.png
  punk4@20x.png
  punk5.png
  punk5@20x.png
  punk6.png
  punk6@20x.png
  punk7.png
  punk7@20x.png
  punk8.png
  punk8@20x.png
  ...
```

Let's open up `punk0.png` to `punk24.png`:

![](i/no1/punk0.png)
![](i/no1/punk1.png)
![](i/no1/punk2.png)
![](i/no1/punk3.png)
![](i/no1/punk4.png)
![](i/no1/punk5.png)
![](i/no1/punk6.png)
![](i/no1/punk7.png)
![](i/no1/punk8.png)
![](i/no1/punk9.png)
![](i/no1/punk10.png)
![](i/no1/punk11.png)
![](i/no1/punk12.png)
![](i/no1/punk13.png)
![](i/no1/punk14.png)
![](i/no1/punk15.png)
![](i/no1/punk16.png)
![](i/no1/punk17.png)
![](i/no1/punk18.png)
![](i/no1/punk19.png)
![](i/no1/punk20.png)
![](i/no1/punk21.png)
![](i/no1/punk22.png)
![](i/no1/punk23.png)
![](i/no1/punk24.png)

And let's have a looksie at the biggie 20x (480×480) versions:

![](i/no1/punk0@20x.png)

![](i/no1/punk1@20x.png)

[...]

![](i/no1/punk24@20x.png)

Note: If you use your own artwork
make sure your type and attribute names
used in the list in the comma-separated values (.CSV) format
match the filenames (without the `.png` extension).
For the matching algorithm all names
get automatically downcased and all spaces deleted,
thus,
`Classic` will map to `classic.png` ![](dodge/classic.png)
and `3D Glasses` to `3dglasses.png` ![](dodge/3dglasses.png)
and `Knitted Cap` to `knittedcap.png` ![](dodge/knittedcap.png)
and so on.

### Step 3 - Bonus: Generate an All-In-One Composite Image

Let's generate an all-in-one composite image holding the complete
collection in a 5×5 grid.

``` ruby
require './boot'

recs = read_csv( './no1/dodge.csv' )
puts "  #{recs.size} punk(s)"
#=>  25 punk(s)

art = Art.new( dir: './dodge' )

## 5×5 grid with every tile 24×24
punks = CompositeImage.new( 5, 5 )

recs.each_with_index do |rec,i|
  punk = art.generate( rec )
  punks << punk
end

punks.save( './o/no1/punks.png')
```

Yes, that's it.
Run the script to generate algorithmically your collection
in an all-in-one composite.

Open up `punks.png` to have a looksie.

![](i/no1/punks.png)

## Collection №2 -  The First One Hundred Pixel Art Punks

Let's take on a bigger pixel art collection.
Let's generate a pixel-perfect copy of the first one hundred punks in the original Larva Labs series in the classic 24×24
format.

Let's copy and (re)use all punk (building) blocks
in the basic series (24×24):

Male 1/2/3/4 ![](basic/male1.png)
![](basic/male2.png)
![](basic/male3.png)
![](basic/male4.png),
Female 1/2/3/4 ![](basic/female1.png)
![](basic/female2.png)
![](basic/female3.png)
![](basic/female4.png),
Zombie ![](basic/zombie.png),
Ape ![](basic/ape.png),
Alien ![](basic/alien.png)

Attributes (by category and a-z):
- Hat -
  Bandana (m/f)
  ![](basic/m/bandana.png)
  ![](basic/f/bandana.png),
  Beanie (m)
  ![](basic/m/beanie.png),
  Cap (m/f)
  ![](basic/m/cap.png)
  ![](basic/f/cap.png),
  Cap Forward (m)
  ![](basic/m/capforward.png),
  Cowboy Hat (m)
  ![](basic/m/cowboyhat.png),
  Do-rag (m)
  ![](basic/m/dorag.png),
  Fedora (m)
  ![](basic/m/fedora.png),
  Headband (m/f)
  ![](basic/m/headband.png)
  ![](basic/f/headband.png),
  Hoodie (m)
  ![](basic/m/hoodie.png),
  Knitted Cap (m/f)
  ![](basic/m/knittedcap.png)
  ![](basic/f/knittedcap.png),
  Pilot Helmet (f)
  ![](basic/f/pilothelmet.png),
  Police Cap (m)
  ![](basic/m/policecap.png),
  Tassle Hat (f)
  ![](basic/f/tasslehat.png),
  Tiara (f)
  ![](basic/f/tiara.png),
  Top Hat (m)
  ![](basic/m/tophat.png)
- Hair -
  Blonde Bob (f)
  ![](basic/f/blondebob.png),
  Blonde Short (f)
  ![](basic/f/blondeshort.png),
  Clown Hair Green (m/f)
  ![](basic/m/clownhairgreen.png)
  ![](basic/f/clownhairgreen.png),
  Crazy Hair (m/f)
  ![](basic/m/crazyhair.png)
  ![](basic/f/crazyhair.png),
  Dark Hair (f)
  ![](basic/f/darkhair.png),
  Frumpy Hair (m/f)
  ![](basic/m/frumpyhair.png)
  ![](basic/f/frumpyhair.png),
  Half Shaved (f)
  ![](basic/f/halfshaved.png),
  Messy Hair (m/f)
  ![](basic/m/messyhair.png)
  ![](basic/f/messyhair.png),
  Mohawk (m/f)
  ![](basic/m/mohawk.png)
  ![](basic/f/mohawk.png),
  Mohawk Dark (m/f)
  ![](basic/m/mohawkdark.png)
  ![](basic/f/mohawkdark.png),
  Mohawk Thin (m/f)
  ![](basic/m/mohawkthin.png)
  ![](basic/f/mohawkthin.png),
  Orange Side (f)
  ![](basic/f/orangeside.png),
  Peak Spike (m)
  ![](basic/m/peakspike.png),
  Pigtails (f)
  ![](basic/f/pigtails.png),
  Pink With Hat (f)
  ![](basic/f/pinkwithhat.png),
  Purple Hair (m)
  ![](basic/m/purplehair.png),
  Red Mohawk (f)
  ![](basic/f/redmohawk.png),
  Shaved Head (m)
  ![](basic/m/shavedhead.png),
  Straight Hair (f)
  ![](basic/f/straighthair.png),
  Straight Hair Blonde (f)
  ![](basic/f/straighthairblonde.png),
  Straight Hair Dark (f)
  ![](basic/f/straighthairdark.png),
  Stringy Hair (m/f)
  ![](basic/m/stringyhair.png)
  ![](basic/f/stringyhair.png),
  Vampire Hair (m)
  ![](basic/m/vampirehair.png),
  Wild Blonde (f)
  ![](basic/f/wildblonde.png),
  Wild Hair (m/f)
  ![](basic/m/wildhair.png)
  ![](basic/f/wildhair.png),
  Wild White Hair (f)
  ![](basic/f/wildwhitehair.png)
- Eyes -
  3D Glasses (m/f)
   ![](basic/m/3dglasses.png)
   ![](basic/f/3dglasses.png),
  Big Shades (m/f)
  ![](basic/m/bigshades.png)
  ![](basic/f/bigshades.png),
  Classic Shades (m/f)
  ![](basic/m/classicshades.png)
  ![](basic/f/classicshades.png),
  Eye Mask (m/f)
  ![](basic/m/eyemask.png)
  ![](basic/f/eyemask.png),
  Eye Patch (m/f)
  ![](basic/m/eyepatch.png)
  ![](basic/f/eyepatch.png),
  Horned Rim Glasses (m/f)
  ![](basic/m/hornedrimglasses.png)
  ![](basic/f/hornedrimglasses.png),
  Nerd Glasses (m/f)
  ![](basic/m/nerdglasses.png)
  ![](basic/f/nerdglasses.png),
  Regular Shades (m/f)
  ![](basic/m/regularshades.png)
  ![](basic/f/regularshades.png),
  Small Shades (m)
  ![](basic/m/smallshades.png),
  VR (m/f)
  ![](basic/m/vr.png)
  ![](basic/f/vr.png),
  Welding Goggles (f)
  ![](basic/f/weldinggoggles.png)
- Eyes (Makeup) -
  Blue Eye Shadow (f)
  ![](basic/f/blueeyeshadow.png),
  Clown Eyes Blue (m/f)
  ![](basic/m/clowneyesblue.png)
  ![](basic/f/clowneyesblue.png),
  Clown Eyes Green (m/f)
  ![](basic/m/clowneyesgreen.png)
  ![](basic/f/clowneyesgreen.png),
  Green Eye Shadow (f)
  ![](basic/f/greeneyeshadow.png),
  Purple Eye Shadow (f)
  ![](basic/f/purpleeyeshadow.png)
- Blemishes -
  Mole (m/f)
   ![](basic/m/mole.png)
   ![](basic/f/mole.png),
  Rosy Cheeks (m/f)
   ![](basic/m/rosycheeks.png)
   ![](basic/f/rosycheeks.png),
  Spots (m/f)
   ![](basic/m/spots.png)
   ![](basic/f/spots.png)
- Nose -
  Clown Nose (m/f)
   ![](basic/m/clownnose.png)
   ![](basic/f/clownnose.png),
- Ears -
  Earring (m/f)
   ![](basic/m/earring.png)
   ![](basic/f/earring.png)
- Mouth -
  Buck Teeth (m)
   ![](basic/m/buckteeth.png),
  Frown (m)
   ![](basic/m/frown.png),
  Smile (m)
   ![](basic/m/smile.png)
- Mouth (Makeup) -
  Black Lipstick (f)
   ![](basic/f/blacklipstick.png),
  Hot Lipstick (f)
   ![](basic/f/hotlipstick.png),
  Purple Lipstick (f)
   ![](basic/f/purplelipstick.png)
- Mouth Prop -
  Cigarette (m/f)
   ![](basic/m/cigarette.png)
   ![](basic/f/cigarette.png),
  Medical Mask (m/f)
   ![](basic/m/medicalmask.png)
   ![](basic/f/medicalmask.png),
  Pipe (m/f)
   ![](basic/m/pipe.png)
   ![](basic/f/pipe.png),
  Vape (m/f)
   ![](basic/m/vape.png)
   ![](basic/f/vape.png)
- Beard -
  Big Beard (m)
   ![](basic/m/bigbeard.png),
  Chinstrap (m)
   ![](basic/m/chinstrap.png),
  Front Beard (m)
   ![](basic/m/frontbeard.png),
  Front Beard Dark (m)
   ![](basic/m/frontbearddark.png),
  Goat (m)
   ![](basic/m/goat.png),
  Handlebars (m)
   ![](basic/m/handlebars.png),
  Luxurious Beard (m)
   ![](basic/m/luxuriousbeard.png),
  Mustache (m)
   ![](basic/m/mustache.png),
  Muttonchops (m)
   ![](basic/m/muttonchops.png),
  Normal Beard (m)
   ![](basic/m/normalbeard.png),
  Normal Beard Black (m)
   ![](basic/m/normalbeardblack.png),
  Shadow Beard (m)
   ![](basic/m/shadowbeard.png)
- Neck Accessory -
  Choker (f)
   ![](basic/f/choker.png),
  Gold Chain (m/f)
   ![](basic/m/goldchain.png)
   ![](basic/f/goldchain.png),
  Silver Chain (m/f)
   ![](basic/m/silverchain.png)
   ![](basic/f/silverchain.png)

(Source: [Punk (Building) Blocks - Basic Series (24×24)](https://github.com/cryptopunksnotdead/punks.blocks#basic-punk-series-2424))

Bonus - Let's add Laser Eyes (m) ![](basic/m/lasereyes.png),
Laser Eyes Gold (m) ![](basic/m/lasereyesgold.png).

### Step 1 -  Generate A List of Your Pixel Art Collection in Tabular Text Format (Spreadsheet)

Let's again generate the list of the pixel art collection
by hand. Let's (re)use the rarity & popularity distribution of
the original series that starts with 56 males and 44 females.

Note: Let's use numbers for the four
male and female (arche)types
skintone variants, that is,
1 - darker, 2 - dark, 3 - light, 4 - lighter.
Example  -  [`punks.csv`](no2/punks.csv):

``` csv
type,        attribute1, attribute2, attribute3, attribute4, attribute5
Female 2, Earring, Blonde Bob, Green Eye Shadow
Male 1, Smile, Mohawk, Laser Eyes
Female 3, Wild Hair
Male 1, Wild Hair, Pipe, Nerd Glasses
Male 2, Goat, Earring, Wild Hair, Big Shades
Female 2, Earring, Half Shaved, Purple Eye Shadow
Male 2, Do-rag, Laser Eyes Gold
Female 2, Spots, Wild White Hair, Clown Eyes Blue
Male 1, Luxurious Beard, Messy Hair
Male 2, Big Beard, Police Cap, Clown Nose
Female 1, Mohawk, Blue Eye Shadow
Female 2, Black Lipstick, Straight Hair Dark, Clown Eyes Green
Female 1, Purple Lipstick, Blonde Short
Female 3, Black Lipstick, Straight Hair Blonde, Big Shades
Female 1, Hot Lipstick, Pilot Helmet, Pipe
Male 4, Luxurious Beard, Wild Hair, Regular Shades
Male 2, Earring, Stringy Hair, Small Shades
Male 3, Frown, Mohawk
Male 2, Muttonchops, Eye Mask
Female 1, Hot Lipstick, Bandana, Horned Rim Glasses
Male 3, Crazy Hair
Male 3, Earring, Messy Hair, Classic Shades
Female 1, Purple Lipstick, Pilot Helmet
Male 3, Handlebars, Earring, Do-rag
Male 3, Smile, Mohawk Dark
Female 1, Wild White Hair
Female 2, Dark Hair
Male 3, Earring, Peak Spike
Male 1, Crazy Hair, Big Shades
Female 2, Earring, Half Shaved, Horned Rim Glasses
Male 1, Normal Beard Black, Cap
Female 1, Stringy Hair
Male 3, Frown, VR
Male 3, Peak Spike
Female 3, Purple Lipstick, Frumpy Hair
Male 1, Normal Beard, Earring, Peak Spike, Cigarette, Horned Rim Glasses
Female 2, Red Mohawk
Female 1, Cap, Clown Eyes Blue
Female 3, Purple Lipstick, Frumpy Hair, Classic Shades
Male 3, Frown, Shaved Head, Regular Shades
Male 3, Smile, Chinstrap, Bandana
Male 1, Muttonchops, Wild Hair, Clown Eyes Green
Female 1, Wild Hair, Cigarette
Female 2, Half Shaved
Male 3, Messy Hair
Male 2, Mole, Earring
Female 2, Purple Lipstick, Straight Hair Dark
Male 1, Knitted Cap
Male 3, Mohawk, Big Shades
Female 1, Bandana, Purple Eye Shadow
Male 2, Spots, Fedora
Male 2, Muttonchops, Earring, Wild Hair
Male 1, Shadow Beard, Earring, Knitted Cap, Nerd Glasses
Female 3, Straight Hair, Big Shades
Male 1, Hoodie
Male 3, Eye Patch
Male 1, Shaved Head
Male 3, Headband
Male 2, Normal Beard Black, Hoodie
Male 4, Muttonchops, Cowboy Hat
Female 3, Purple Lipstick, Stringy Hair
Female 3, Dark Hair
Female 1, Hot Lipstick, Earring, Mohawk Dark, Clown Eyes Blue
Male 1, Earring, Shaved Head, Small Shades
Female 2, Purple Lipstick, Straight Hair Blonde
Female 2, Headband, Eye Mask
Female 1, Tassle Hat
Female 1, Purple Lipstick, Half Shaved, 3D Glasses
Male 1, Do-rag, Nerd Glasses
Male 2, Normal Beard Black, Fedora
Female 2, Earring, Crazy Hair, Cigarette, Regular Shades
Female 3, Purple Lipstick, Half Shaved, Green Eye Shadow
Male 1, Normal Beard, Earring, Fedora, Nerd Glasses
Female 2, Black Lipstick, Earring, Bandana
Male 1, Shadow Beard, Earring, Mohawk Dark
Male 3, Shadow Beard, Knitted Cap, Eye Patch
Male 3, Do-rag, Cigarette
Male 1, Handlebars, Stringy Hair
Male 2, Handlebars, Knitted Cap, Eye Patch
Male 1, Normal Beard, Mohawk Dark
Male 4, Mustache, Wild Hair
Male 3, Headband, Small Shades
Male 4, Headband
Male 3, Shaved Head, Vape, Small Shades
Male 3, Earring, Messy Hair, Big Shades
Female 1, Crazy Hair
Female 1, Hot Lipstick, Earring, Knitted Cap, Green Eye Shadow
Male 3, Earring, Hoodie
Female 3, Hot Lipstick, Cap
Female 1, Choker
Male 2, Hoodie, Eye Patch
Female 3, Straight Hair, Clown Nose
Female 3, Purple Lipstick, Pink With Hat, Nerd Glasses
Female 3, Bandana
Female 1, Earring, Pink With Hat, Regular Shades
Male 2, Earring, Mohawk
Female 3, Blonde Bob
Male 2, Wild Hair
Female 3, Black Lipstick, Dark Hair, Welding Goggles
Male 2, Hoodie, Cigarette
```

Note - see if you can spot the never-befor-seen super rare punks
with Laser Eyes and Laser Eyes Gold.

### Step 2  - Generate All Pixel Art Images in Original Format and In 20x

Now the "magic" let's read in the list
in the comma-separated values (.CSV) format
and generate all pixel art images in the original format
(24×24) and in 20x (480×480).

``` ruby
require './boot'

recs = read_csv( './no2/punks.csv' )
puts "  #{recs.size} punk(s)"
#=>  100 punk(s)

art = Art.new(
        dir: './basic',
        qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                             'Zombie',
                             'Ape',
                             'Alien'],
                      'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4'],
                   })

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk.save( "./o/no2/#{name}.png" )
  punk.zoom(20).save( "./o/no2/#{name}@20x.png" )
end
```

Note: The attributes (see above) come
in two editions, that is,  m - male (stored in the `/m` directory)
and f - female (stored in the `/f` directory).
To map the attribute (or "qualify"
the name with an extra auto-added directory)
let's configure
the "qualifier" by archetype, that is,
Male 1/2/3/4, Zombie, Ape and Alien will auto-add the `/m` directory
to the attribute image lookup and
Female 1/2/3/4 will aut-add the `/f` directory.

Yes, that's it.
Run the script to generate algorithmically your collection.

In the `/o` directory you will now find
two images per punk - in the orginal format, that is, 24×24
and 20x, that is, 480×480 - and get:

```
no2/
  punk0.png
  punk0@20x.png
  punk1.png
  punk1@20x.png
  punk2.png
  punk2@20x.png
  punk3.png
  punk3@20x.png
  punk4.png
  punk4@20x.png
  punk5.png
  punk5@20x.png
  punk6.png
  punk6@20x.png
  punk7.png
  punk7@20x.png
  punk8.png
  punk8@20x.png
  ...
```

Let's open up `punk0.png` to `punk99.png`:

![](i/no2/punk0.png)
![](i/no2/punk1.png)
![](i/no2/punk2.png)
![](i/no2/punk3.png)
![](i/no2/punk4.png)
![](i/no2/punk5.png)
![](i/no2/punk6.png)
![](i/no2/punk7.png)
![](i/no2/punk8.png)
![](i/no2/punk9.png)
![](i/no2/punk10.png)
![](i/no2/punk11.png)
![](i/no2/punk12.png)
![](i/no2/punk13.png)
![](i/no2/punk14.png)
![](i/no2/punk15.png)
![](i/no2/punk16.png)
![](i/no2/punk17.png)
![](i/no2/punk18.png)
![](i/no2/punk19.png)
![](i/no2/punk20.png)
![](i/no2/punk21.png)
![](i/no2/punk22.png)
![](i/no2/punk23.png)
![](i/no2/punk24.png)
![](i/no2/punk25.png)
![](i/no2/punk26.png)
![](i/no2/punk27.png)
![](i/no2/punk28.png)
![](i/no2/punk29.png)
![](i/no2/punk30.png)
![](i/no2/punk31.png)
![](i/no2/punk32.png)
![](i/no2/punk33.png)
![](i/no2/punk34.png)
![](i/no2/punk35.png)
![](i/no2/punk36.png)
![](i/no2/punk37.png)
![](i/no2/punk38.png)
![](i/no2/punk39.png)
![](i/no2/punk40.png)
![](i/no2/punk41.png)
![](i/no2/punk42.png)
![](i/no2/punk43.png)
![](i/no2/punk44.png)
![](i/no2/punk45.png)
![](i/no2/punk46.png)
![](i/no2/punk47.png)
![](i/no2/punk48.png)
![](i/no2/punk49.png)
![](i/no2/punk50.png)
![](i/no2/punk51.png)
![](i/no2/punk52.png)
![](i/no2/punk53.png)
![](i/no2/punk54.png)
![](i/no2/punk55.png)
![](i/no2/punk56.png)
![](i/no2/punk57.png)
![](i/no2/punk58.png)
![](i/no2/punk59.png)
![](i/no2/punk60.png)
![](i/no2/punk61.png)
![](i/no2/punk62.png)
![](i/no2/punk63.png)
![](i/no2/punk64.png)
![](i/no2/punk65.png)
![](i/no2/punk66.png)
![](i/no2/punk67.png)
![](i/no2/punk68.png)
![](i/no2/punk69.png)
![](i/no2/punk70.png)
![](i/no2/punk71.png)
![](i/no2/punk72.png)
![](i/no2/punk73.png)
![](i/no2/punk74.png)
![](i/no2/punk75.png)
![](i/no2/punk76.png)
![](i/no2/punk77.png)
![](i/no2/punk78.png)
![](i/no2/punk79.png)
![](i/no2/punk80.png)
![](i/no2/punk81.png)
![](i/no2/punk82.png)
![](i/no2/punk83.png)
![](i/no2/punk84.png)
![](i/no2/punk85.png)
![](i/no2/punk86.png)
![](i/no2/punk87.png)
![](i/no2/punk88.png)
![](i/no2/punk89.png)
![](i/no2/punk90.png)
![](i/no2/punk91.png)
![](i/no2/punk92.png)
![](i/no2/punk93.png)
![](i/no2/punk94.png)
![](i/no2/punk95.png)
![](i/no2/punk96.png)
![](i/no2/punk97.png)
![](i/no2/punk98.png)
![](i/no2/punk99.png)

And let's have a looksie at the biggie 20x (480×480) versions:

![](i/no2/punk0@20x.png)

![](i/no2/punk1@20x.png)

[...]

![](i/no2/punk99@20x.png)

### Step 3 - Bonus: Generate an All-In-One Composite Image

Again let's generate an all-in-one composite image holding the complete
collection in a 10×10 grid.

``` ruby
require './boot'

recs = read_csv( './no2/punks.csv' )
puts "  #{recs.size} punk(s)"
#=>  100 punk(s)

art = Art.new(
        dir: './basic',
        qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                             'Zombie',
                             'Ape',
                             'Alien'],
                      'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4'],
                   })

## 10×10 grid with every tile 24×24
punks = CompositeImage.new( 10, 10 )

recs.each_with_index do |rec,i|
  punk = art.generate( rec )
  punks << punk
end

punks.save( './o/no2/punks.png')
```

Yes, that's it.
Run the script to generate algorithmically your collection
in an all-in-one composite.

Open up `punks.png` to have a looksie.

![](i/no2/punks.png)

## Collection №3 -  The First One Hundred Pixel Art Punks - The Black & White (Pencil Drawing) Sketch Edition

Let's redo the the first one hundred pixel art punks collection
and let's pick a pixel art special effect.

Tip:  See [**Pixel Art Special Effects Gallery / Cheatsheet - Free Ideas & Recipes for Starting Your Own CryptoPunks Collection Today**](https://github.com/cryptopunksnotdead/programming-cryptopunks/blob/master/A1_special_effects.md)
in the free (online) Programming CryptoPunks & Copypasta Step-by-Step Book(let).

### Step 1 -  Generate A List of Your Pixel Art Collection in Tabular Text Format (Spreadsheet)

Same as in collection №2 above.
Let's (re)use [`punks.csv`](no3/punks.csv).

### Step 2  - Generate All Pixel Art Images in Original Format and In 20x

Same as in collection №2 above.
Plus let's try the black & white (pencil drawing) sketch special effect.

``` ruby
#  [..]

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk_sketch = punk.sketch( 1, line: 1 )
  punk_sketch.save( "./o/no3/#{name}.png" )

  punk_sketch = punk.sketch( 20, line: 4 )
  punk_sketch.save( "./o/no3/#{name}@20x.png" )
end
```

Note: The minimum format for a black & white (pencil drawing) sketch
is 49×49px (24\*1 + 25\*1 = 49px), that is,  24 "inner" white pixels and 25 "outline" black pixels.
And the 20x version becomes 580×580px (20\*24 + 4\*25 = 580px),
that is, with a 24\*20 "inner"
white pixel and 25\*4 "outline" black pixels.

Yes, that's it.
Run the script to generate algorithmically your collection.

In the `/o` directory you will now find
two images per punk - in the orginal format, that is, 49×49
and 20x, that is, 580×580 - and get:

```
no3/
  punk0.png
  punk0@20x.png
  punk1.png
  punk1@20x.png
  punk2.png
  punk2@20x.png
  punk3.png
  punk3@20x.png
  punk4.png
  punk4@20x.png
  punk5.png
  punk5@20x.png
  punk6.png
  punk6@20x.png
  punk7.png
  punk7@20x.png
  punk8.png
  punk8@20x.png
  ...
```

Let's open up `punk0.png` to `punk99.png`:

![](i/no3/punk0.png)
![](i/no3/punk1.png)
![](i/no3/punk2.png)
![](i/no3/punk3.png)
![](i/no3/punk4.png)
![](i/no3/punk5.png)
![](i/no3/punk6.png)
![](i/no3/punk7.png)
![](i/no3/punk8.png)
![](i/no3/punk9.png)
![](i/no3/punk10.png)
![](i/no3/punk11.png)
![](i/no3/punk12.png)
![](i/no3/punk13.png)
![](i/no3/punk14.png)
![](i/no3/punk15.png)
![](i/no3/punk16.png)
![](i/no3/punk17.png)
![](i/no3/punk18.png)
![](i/no3/punk19.png)
![](i/no3/punk20.png)
![](i/no3/punk21.png)
![](i/no3/punk22.png)
![](i/no3/punk23.png)
![](i/no3/punk24.png)
![](i/no3/punk25.png)
![](i/no3/punk26.png)
![](i/no3/punk27.png)
![](i/no3/punk28.png)
![](i/no3/punk29.png)
![](i/no3/punk30.png)
![](i/no3/punk31.png)
![](i/no3/punk32.png)
![](i/no3/punk33.png)
![](i/no3/punk34.png)
![](i/no3/punk35.png)
![](i/no3/punk36.png)
![](i/no3/punk37.png)
![](i/no3/punk38.png)
![](i/no3/punk39.png)
![](i/no3/punk40.png)
![](i/no3/punk41.png)
![](i/no3/punk42.png)
![](i/no3/punk43.png)
![](i/no3/punk44.png)
![](i/no3/punk45.png)
![](i/no3/punk46.png)
![](i/no3/punk47.png)
![](i/no3/punk48.png)
![](i/no3/punk49.png)
![](i/no3/punk50.png)
![](i/no3/punk51.png)
![](i/no3/punk52.png)
![](i/no3/punk53.png)
![](i/no3/punk54.png)
![](i/no3/punk55.png)
![](i/no3/punk56.png)
![](i/no3/punk57.png)
![](i/no3/punk58.png)
![](i/no3/punk59.png)
![](i/no3/punk60.png)
![](i/no3/punk61.png)
![](i/no3/punk62.png)
![](i/no3/punk63.png)
![](i/no3/punk64.png)
![](i/no3/punk65.png)
![](i/no3/punk66.png)
![](i/no3/punk67.png)
![](i/no3/punk68.png)
![](i/no3/punk69.png)
![](i/no3/punk70.png)
![](i/no3/punk71.png)
![](i/no3/punk72.png)
![](i/no3/punk73.png)
![](i/no3/punk74.png)
![](i/no3/punk75.png)
![](i/no3/punk76.png)
![](i/no3/punk77.png)
![](i/no3/punk78.png)
![](i/no3/punk79.png)
![](i/no3/punk80.png)
![](i/no3/punk81.png)
![](i/no3/punk82.png)
![](i/no3/punk83.png)
![](i/no3/punk84.png)
![](i/no3/punk85.png)
![](i/no3/punk86.png)
![](i/no3/punk87.png)
![](i/no3/punk88.png)
![](i/no3/punk89.png)
![](i/no3/punk90.png)
![](i/no3/punk91.png)
![](i/no3/punk92.png)
![](i/no3/punk93.png)
![](i/no3/punk94.png)
![](i/no3/punk95.png)
![](i/no3/punk96.png)
![](i/no3/punk97.png)
![](i/no3/punk98.png)
![](i/no3/punk99.png)

And let's have a looksie at the biggie 20x (580×580) versions:

![](i/no3/punk0@20x.png)

![](i/no3/punk1@20x.png)

[...]

![](i/no3/punk99@20x.png)

### Step 3 - Bonus: Generate an All-In-One Composite Image

Same as in collection №2 above.
Plus let's try the black & white (pencil drawing) sketch special effect.

``` ruby
#  [..]

## 10×10 grid with every tile 49×49
punks = CompositeImage.new( 10, 10, width: 49, height: 49 )

recs.each_with_index do |rec,i|
  punk = art.generate( rec )

  punk_sketch = punk.sketch( 1, line: 1 )

  punks << punk_sketch
end

punks.save( './o/no3/punks.png')
```

Yes, that's it.
Run the script to generate algorithmically your collection
in an all-in-one composite.

Open up `punks.png` to have a looksie.

![](i/no3/punks.png)

## Collection №4 - 111 Cool Blue Aliens (Incl. Females & Super-Rare Apes)

Let's try a super-rare never-before-seen pixel art collection.
Let's generate a pixel-perfect copy of all nine
super-rare cool blue humanoid aliens
in the original Larva Labs series in the classic 24×24
format
and for fun let's add a hundred more
super-rare never-before-seen aliens incl. females and apes.

Let's copy and (re)use punk (building) blocks
in the female extension 50/50 series (24×24):

Alien Female ![](basic/alienfemale.png)

And in the planet of apes series (24×24):

Alien Ape ![](basic/alienape.png)

Bonus - Let's add from the int'l headwear series (24×24)
for more fun:

Bear Skin (m)  ![](basic/m/bearskin.png),
Beret (m)  ![](basic/m/beret.png),
Boater (m)  ![](basic/m/boater.png),
Fez (m)  ![](basic/m/fez.png),
Flamenco Hat (f)  ![](basic/f/flamencohat.png),
Flower Crown (f)  ![](basic/f/flowercrown.png),
Panama Hat (f)  ![](basic/f/panamahat.png),
Royal Cocktail Hat (f) ![](basic/f/royalcocktailhat.png),
Tuque (m)  ![](basic/m/tuque.png),
Turban (m)  ![](basic/m/turban.png),
Tyrolean (f) ![](basic/f/tyrolean.png)

### Step 1 -  Generate A List of Your Pixel Art Collection in Tabular Text Format (Spreadsheet)

Let's again generate the list of the pixel art collection
by hand. Let's start with
the nine super-rare million-dollar aliens from the original LarvaLabs series -
incl. the top-selling (~$8 million in March, 11th 2021)
pipe-smoking "Mr Mona Lisa" -
and wrap up with a hundred never-before-seen aliens
incl. females and three alien apes.

Yes, let's add some laser eyes to get the buy-in from
the bitcon maxi club.

Example  -  [`aliens.csv`](no4/aliens.csv):

``` csv
type, attribute1, attribute2, attribute3, attribute4, attribute5
Alien, Bandana, Regular Shades
Alien, Cap
Alien, Headband
Alien, Earring, Cowboy Hat
Alien, Bandana, Laser Eyes
Alien, Do-rag, Small Shades
Alien, Earring, Knitted Cap
Alien, Earring, Knitted Cap, Medical Mask
Alien, Cap Forward, Small Shades, Pipe
Alien Female, Tiara, Rosy Cheeks
Alien Female, Pigtails, Gold Chain
Alien Female, Orange Side, 3D Glasses
Alien Female, Wild Blonde
Alien Female, Knitted Cap, Pipe
Alien Female, Mohawk Dark, VR
Alien Female, Half Shaved, Small Shades
Alien, Mohawk Dark, VR
Alien, Knitted Cap
Alien, Crazy Hair, Gold Chain
Alien, Top Hat, Laser Eyes Gold
Alien, Hoodie, Silver Chain
Alien, Police Cap
Alien, Purple Hair, Laser Eyes
Alien, Beanie, Small Shades, Gold Chain
Alien, Vampire Hair, Buck Teeth
Alien, Clown Hair Green, Clown Nose
Alien, Bear Skin, 3D Glasses, Vape
Alien, Mohawk Dark, Eye Mask
Alien, Peak Spike
Alien, Do-rag
Alien, Boater, Buck Teeth, Classic Shades, Cigarette
Alien, Beanie, Cigarette
Alien Female, Wild White Hair, 3D Glasses
Alien, Cowboy Hat, VR
Alien Female, Panama Hat, Spots
Alien Female, Flamenco Hat
Alien, Frumpy Hair, 3D Glasses, Cigarette
Alien, Cap, Laser Eyes Gold
Alien Ape, Bandana
Alien Female, Tyrolean, Classic Shades
Alien, Hoodie, Big Shades
Alien, Knitted Cap, Small Shades
Alien, Hoodie, 3D Glasses, Pipe
Alien, Peak Spike, Silver Chain
Alien Female, Tassle Hat
Alien Female, Pilot Helmet, Gold Chain
Alien, Crazy Hair, Small Shades, Gold Chain
Alien, Fedora, Small Shades
Alien, Do-rag, Classic Shades
Alien, Police Cap, Small Shades
Alien, Earring, Purple Hair
Alien, Top Hat, Medical Mask
Alien, Cap, Nerd Glasses, Cigarette
Alien, Turban
Alien Female, Red Mohawk
Alien Female, Sombrero, Medical Mask
Alien Female, Top Hat, Gold Chain
Alien Female, Beanie, Rosy Cheeks
Alien Female, Mohawk Dark, Clown Eyes Blue, Choker
Alien, Hoodie, 3D Glasses, Gold Chain, Pipe
Alien, Earring, Mohawk Dark, Regular Shades
Alien Female, Fedora, Gold Chain
Alien Female, Earring, Cowboy Hat, Regular Shades
Alien, Cowboy Hat, Laser Eyes
Alien, Earring, Shaved Head, Eye Mask
Alien Female, Earring, Royal Cocktail Hat, Clown Eyes Green
Alien, Bandana
Alien, Do-rag, Eye Patch
Alien Female, Tiara, Clown Eyes Green
Alien Female, Police Cap, Regular Shades, Gold Chain
Alien Female, Earring, Cap, Clown Eyes Blue
Alien, Stringy Hair, Small Shades, Mole
Alien Female, Sombrero, Big Shades
Alien, Cap Forward, Clown Eyes Blue, Silver Chain
Alien, Vampire Hair, Clown Nose
Alien Ape, Knitted Cap
Alien, Clown Hair Green, Eye Mask
Alien, Earring, Cap, 3D Glasses
Alien, Mohawk Dark, Big Shades, Cigarette
Alien Female, Earring, Tyrolean, Regular Shades
Alien Female, Wild White Hair, Clown Eyes Blue
Alien, Beanie, Eye Patch
Alien, Crazy Hair, Small Shades, Silver Chain
Alien, Tuque, 3D Glasses
Alien Female, Hoodie, Clown Eyes Green
Alien, Crazy Hair
Alien, Earring, Cap Forward, 3D Glasses, Vape
Alien Female, Pigtails, Nerd Glasses, Cigarette
Alien Female, Clown Hair Green, Eye Patch
Alien, Earring, Vampire Hair, Small Shades, Pipe
Alien Female, Flower Crown, Rosy Cheeks, Regular Shades, Gold Chain, Cigarette
Alien Female, Do-rag, Eye Patch
Alien, Fedora, Small Shades, Pipe
Alien, Beret, VR, Buck Teeth
Alien, Frumpy Hair, Regular Shades, Cigarette
Alien, Knitted Cap, Laser Eyes Gold, Cigarette
Alien, Earring, Bandana
Alien Female, Panama Hat, Gold Chain
Alien Female, Tiara, Half Shaved
Alien, Earring, Cap Forward
Alien, Mohawk Thin, Mole
Alien, Fez, Cigarette
Alien Female, Flamenco Hat, Classic Shades
Alien Ape, Hoodie
Alien Female, Pilot Helmet, Clown Eyes Green, Choker
Alien, Purple Hair, Medical Mask
Alien, Stringy Hair, Eye Patch
Alien, Earring, Regular Shades, Gold Chain
Alien, Knitted Cap, Laser Eyes
Alien Female, Purple Hair, Big Shades
Alien Female, Royal Cocktail Hat, 3D Glasses
```

### Step 2  - Generate All Pixel Art Images in Original Format and In 20x

Now the "magic" let's read in the list
in the comma-separated values (.CSV) format
and generate all pixel art images in the original format
(24×24) and in 20x (480×480).

Bonus - Let's
generate an all-in-one composite image holding the complete
collection in a 10×12 grid.

``` ruby
require './boot'

recs = read_csv( './no4/aliens.csv' )
puts "  #{recs.size} punk(s)"
#=>  111 punk(s)

art = Art.new(
        dir: './basic',
        qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                             'Zombie',
                             'Ape',
                             'Alien', 'Alien Ape'],
                     'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4',
                             'Alien Female'],
                   })

## 10x12 grid with every tile 24×24
punks = CompositeImage.new( 10, 12 )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk.save( "./o/no4/#{name}.png" )
  punk.zoom(20).save( "./o/no4/#{name}@20x.png" )

  punks << punk  ## add to composite
end

punks.save( './o/no4/punks.png')   ## save composite
```

What's different from
Collection №2 -  The First One Hundred Pixel Art Punks?

Two new achetypes, that is, Alien Ape and Alien Female in
the qualifier setting / configuration.
Yes, that's it.
Run the script to generate algorithmically your collection.

In the `/o` directory you will now find
two images per punk - in the orginal format, that is, 24×24
and 20x, that is, 480×480 - and get:

```
no4/
  punk0.png
  punk0@20x.png
  punk1.png
  punk1@20x.png
  punk2.png
  punk2@20x.png
  punk3.png
  punk3@20x.png
  punk4.png
  punk4@20x.png
  punk5.png
  punk5@20x.png
  punk6.png
  punk6@20x.png
  punk7.png
  punk7@20x.png
  punk8.png
  punk8@20x.png
  ...
```

Let's open up `punk0.png` to `punk110.png`:

![](i/no4/punk0.png)
![](i/no4/punk1.png)
![](i/no4/punk2.png)
![](i/no4/punk3.png)
![](i/no4/punk4.png)
![](i/no4/punk5.png)
![](i/no4/punk6.png)
![](i/no4/punk7.png)
![](i/no4/punk8.png)
![](i/no4/punk9.png)
![](i/no4/punk10.png)
![](i/no4/punk11.png)
![](i/no4/punk12.png)
![](i/no4/punk13.png)
![](i/no4/punk14.png)
![](i/no4/punk15.png)
![](i/no4/punk16.png)
![](i/no4/punk17.png)
![](i/no4/punk18.png)
![](i/no4/punk19.png)
![](i/no4/punk20.png)
![](i/no4/punk21.png)
![](i/no4/punk22.png)
![](i/no4/punk23.png)
![](i/no4/punk24.png)
![](i/no4/punk25.png)
![](i/no4/punk26.png)
![](i/no4/punk27.png)
![](i/no4/punk28.png)
![](i/no4/punk29.png)
![](i/no4/punk30.png)
![](i/no4/punk31.png)
![](i/no4/punk32.png)
![](i/no4/punk33.png)
![](i/no4/punk34.png)
![](i/no4/punk35.png)
![](i/no4/punk36.png)
![](i/no4/punk37.png)
![](i/no4/punk38.png)
![](i/no4/punk39.png)
![](i/no4/punk40.png)
![](i/no4/punk41.png)
![](i/no4/punk42.png)
![](i/no4/punk43.png)
![](i/no4/punk44.png)
![](i/no4/punk45.png)
![](i/no4/punk46.png)
![](i/no4/punk47.png)
![](i/no4/punk48.png)
![](i/no4/punk49.png)
![](i/no4/punk50.png)
![](i/no4/punk51.png)
![](i/no4/punk52.png)
![](i/no4/punk53.png)
![](i/no4/punk54.png)
![](i/no4/punk55.png)
![](i/no4/punk56.png)
![](i/no4/punk57.png)
![](i/no4/punk58.png)
![](i/no4/punk59.png)
![](i/no4/punk60.png)
![](i/no4/punk61.png)
![](i/no4/punk62.png)
![](i/no4/punk63.png)
![](i/no4/punk64.png)
![](i/no4/punk65.png)
![](i/no4/punk66.png)
![](i/no4/punk67.png)
![](i/no4/punk68.png)
![](i/no4/punk69.png)
![](i/no4/punk70.png)
![](i/no4/punk71.png)
![](i/no4/punk72.png)
![](i/no4/punk73.png)
![](i/no4/punk74.png)
![](i/no4/punk75.png)
![](i/no4/punk76.png)
![](i/no4/punk77.png)
![](i/no4/punk78.png)
![](i/no4/punk79.png)
![](i/no4/punk80.png)
![](i/no4/punk81.png)
![](i/no4/punk82.png)
![](i/no4/punk83.png)
![](i/no4/punk84.png)
![](i/no4/punk85.png)
![](i/no4/punk86.png)
![](i/no4/punk87.png)
![](i/no4/punk88.png)
![](i/no4/punk89.png)
![](i/no4/punk90.png)
![](i/no4/punk91.png)
![](i/no4/punk92.png)
![](i/no4/punk93.png)
![](i/no4/punk94.png)
![](i/no4/punk95.png)
![](i/no4/punk96.png)
![](i/no4/punk97.png)
![](i/no4/punk98.png)
![](i/no4/punk99.png)
![](i/no4/punk100.png)
![](i/no4/punk101.png)
![](i/no4/punk102.png)
![](i/no4/punk103.png)
![](i/no4/punk104.png)
![](i/no4/punk105.png)
![](i/no4/punk106.png)
![](i/no4/punk107.png)
![](i/no4/punk108.png)
![](i/no4/punk109.png)
![](i/no4/punk110.png)

And let's have a looksie at the biggie 20x (480×480) versions:

![](i/no4/punk0@20x.png)

![](i/no4/punk1@20x.png)

[...]

![](i/no4/punk110@20x.png)

And open up `punks.png` for the all-in-one composite:

![](i/no4/punks.png)

To be continued...

## Appendix

### Frequently Asked Questions (F.A.Qs)

**Q:  Can I use a different pixel format for the collection e.g. 16×16 (instead of the classic 24×24)?**

A: Yes,  it works with any square pixel size (e.g. 16×16, 24×24, 32×32).
Note: If you use the `CompositeImage` the default width × height is set to 24 × 24 - if you use a different size
you have to pass in width and height e.g.

``` ruby
# 5×5 grid with every tile 16×16
composite = CompositeImage.new( 5, 5, width: 16, height: 16 )
```

**Q: I am trying to make a composite of 30 variations with a base size of 125×125 px each. I can't seem to figure out how to adjust the settings to make it generate a composite without all of the results overlapping each other. Any ideas?**

The CompositeImage defaults to width: 24, height: 24, thus, if you use a different dimension / canvas
you have to pass along the "custom" width and height.
Example with a 10×3 grid (equal to 30 tiles / variations)  and 125×125 width x height:

``` ruby
composite = CompositeImage.new( 10, 3, width: 125, height: 125 )
```

**Q: Is it possible to have more attributes, that is, seven instead of the five used in the sample collection №2?**

Yes, you can use as many attributes as you like
(no limit really other than the computer processing power and memory) for the scripts / collections.

If you want to use seven attributes - add two more attributes to the tabular dataset - change:

```
type, attribute1, attribute2, attribute3, attribute4, attribute5
Female 2, Earring, Blonde Bob, Green Eye Shadow
...
```

to
```
type, attribute1, attribute2, attribute3, attribute4, attribute5, attribute6, attribute7
Female 2, Earring, Blonde Bob, Green Eye Shadow
...
```

That's it.  By the way - the column names are "arbitrary", that is, yes you can use any name e.g. try hair, eyes, mouth, neck or such instead of attribute1, attribute2, etc.

### Troubleshooting

**Q: I cannot get the `./generate.rb` script to run [in Powershell / in Sublime / in ...]?**

A: Make sure you run the script in the "top-level", that is, `/punks.starter` and you use `no1/generate.rb` or `no2/generate.rb` or `no3/generate.rb`:

```
/punks.starter
   boot.rb
   /no1
      dodge.csv
      generate.rb
   /no2
      punks.csv
      generate.rb
   /no3
      punks.csv
      generate.rb
```

And run inside the `/punks.starter` directory:

```
$ ruby no1/generate.rb          # start in /punks.starter
```

## Questions? Comments?

Post them on the [D.I.Y. Punk (Pixel) Art reddit](https://old.reddit.com/r/DIYPunkArt). Thanks.


text:
###############
# to run use:
#
#   $ ruby no5/generate.rb

require './boot'

recs = read_csv( './no5/aliens.csv' )
puts "  #{recs.size} punk(s)"
#=>  111 punk(s)

qualifier = { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                      'Zombie',
                     'Ape',
                     'Alien', 'Alien Ape'],
              'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4',
                      'Alien Female'],
            }

#################
#  cyber green (hue 90°)

art_a = Art.new( dir: './basic',
               qualifier: qualifier,
               mapping: {
                  'Alien'        => 'alien_90',
                  'Alien Ape'    => 'alienape_90',
                  'Alien Female' => 'alienfemale_90',
               }
             )

## 10x12 grid with every tile 24x24
punks_a = CompositeImage.new( 10, 12 )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art_a.generate( rec )

  punk.save( "./o/no5a/#{name}.png" )
  punk.zoom(20).save( "./o/no5a/#{name}@20x.png" )

  punks_a << punk  ## add to composite
end

punks_a.save( './o/no5a/punks.png')   ## save composite
punks_a.zoom(2).save( './tmp/no5a/punks@2x.png' )   ## save composite

#################
#  burning red (hue 0°)

art_b = Art.new( dir: './basic',
               qualifier: qualifier,
               mapping: {
                  'Alien'        => 'alien_0',
                  'Alien Ape'    => 'alienape_0',
                  'Alien Female' => 'alienfemale_0',
               }
             )

## 10x12 grid with every tile 24x24
punks_b = CompositeImage.new( 10, 12 )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art_b.generate( rec )

  punk.save( "./o/no5b/#{name}.png" )
  punk.zoom(20).save( "./o/no5b/#{name}@20x.png" )

  punks_b << punk  ## add to composite
end

punks_b.save( './o/no5b/punks.png')   ## save composite
punks_b.zoom(2).save( './tmp/no5b/punks@2x.png' )   ## save composite


text:
###############
# to run use:
#
#   $ ruby no1/generate.rb

require './boot'

recs = read_csv( './no1/dodge.csv' )
puts "  #{recs.size} punk(s)"
#=>  25 punk(s)

art = Art.new( dir: './dodge' )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk.save( "./o/no1/#{name}.png" )
  punk.zoom(20).save( "./o/no1/#{name}@20x.png" )
end


text:
###############
# to run use:
#
#   $ ruby no1/composite.rb

require './boot'

recs = read_csv( './no1/dodge.csv' )
puts "  #{recs.size} punk(s)"
#=>  25 punk(s)

art = Art.new( dir: './dodge' )

## 5x5 grid with every tile 24x24
punks = CompositeImage.new( 5, 5 )

recs.each_with_index do |rec,i|
  punk = art.generate( rec )
  punks << punk
end

punks.save( './o/no1/punks.png')


text:
###############
# to run use:
#
#   $ ruby no4/generate.rb

require './boot'

recs = read_csv( './no4/aliens.csv' )
puts "  #{recs.size} punk(s)"
#=>  111 punk(s)

art = Art.new( dir: './basic',
               qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                                    'Zombie',
                                    'Ape',
                                    'Alien', 'Alien Ape'],
                            'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4',
                                    'Alien Female'],
                          }
             )

## 10x12 grid with every tile 24x24
punks = CompositeImage.new( 10, 12 )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk.save( "./o/no4/#{name}.png" )
  punk.zoom(20).save( "./o/no4/#{name}@20x.png" )

  punks << punk  ## add to composite
end

punks.save( './o/no4/punks.png')   ## save composite
punks.zoom(2).save( './tmp/no4/punks@2x.png' )   ## save composite


text:
###############
# to run use:
#
#   $ ruby no2/generate.rb

require './boot'

recs = read_csv( './no2/punks.csv' )
puts "  #{recs.size} punk(s)"
#=>  100 punk(s)

art = Art.new( dir: './basic',
               qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                                    'Zombie',
                                    'Ape',
                                    'Alien'],
                            'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4'],
                          }
             )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  punk.save( "./o/no2/#{name}.png" )
  punk.zoom(20).save( "./o/no2/#{name}@20x.png" )
end


text:
###############
# to run use:
#
#   $ ruby no2/composite.rb

require './boot'

recs = read_csv( './no2/punks.csv' )
puts "  #{recs.size} punk(s)"
#=>  100 punk(s)

art = Art.new( dir: './basic',
               qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                                    'Zombie',
                                    'Ape',
                                    'Alien'],
                            'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4'],
                          }
             )

## 10x10 grid with every tile 24x24
punks = CompositeImage.new( 10, 10 )

recs.each_with_index do |rec,i|
  punk = art.generate( rec )
  punks << punk
end

punks.save( './o/no2/punks.png')


text:
###############
# to run use:
#
#   $ ruby no3/generate.rb

require './boot'

recs = read_csv( './no3/punks.csv' )
puts "  #{recs.size} punk(s)"
#=>  100 punk(s)

art = Art.new( dir: './basic',
               qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                                    'Zombie',
                                    'Ape',
                                    'Alien'],
                            'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4'],
                          }
             )

recs.each_with_index do |rec,i|
  name = "punk#{i}"
  punk = art.generate( rec )

  # note: 49x49px (24 inner pixels + 25 outline pixels = 49px minimum sketch size
  punk_sketch = punk.sketch( 1, line: 1 )
  punk_sketch.save( "./o/no3/#{name}.png" )

  # 580x580px (20 *24 + 4+25 = 580px)
  punk_sketch = punk.sketch( 20, line: 4 )
  punk_sketch.save( "./o/no3/#{name}@20x.png" )
end


text:
###############
# to run use:
#
#   $ ruby no3/composite.rb

require './boot'

recs = read_csv( './no3/punks.csv' )
puts "  #{recs.size} punk(s)"
#=>  100 punk(s)

art = Art.new( dir: './basic',
               qualifier: { 'm' => ['Male 1', 'Male 2', 'Male 3', 'Male 4',
                                    'Zombie',
                                    'Ape',
                                    'Alien'],
                            'f' => ['Female 1', 'Female 2', 'Female 3', 'Female 4'],
                          }
             )

## 10x10 grid with every tile 49x49
punks = CompositeImage.new( 10, 10, width: 49, height: 49 )

recs.each_with_index do |rec,i|
  punk = art.generate( rec )

  punk_sketch = punk.sketch( 1, line: 1 )

  punks << punk_sketch
end

punks.save( './o/no3/punks.png')


text:
# Comprehensive Exam Question Banks (VIT)

**Note**: Compilation of all question banks for Vellore Institute of Technology up until 2020. No more question banks will be added - this repo is now archived.

## Branches Available

```
BBT
BCB
BCE
BCI
BCL
BCM
BEC
BEE
BEI
BIS
BIT
BMD
BME
BPI
```

## Disclaimer

* For educational purposes only.
* jacob5412/comprehensive-VIT is licensed under the Creative Commons Zero v1.0 Universal


text:
BitwigGrids
-----------

This repository contains some generative music compositions with [Bitwig Grid](https://www.bitwig.com/learnings/getting-around-in-the-grid-39/).

You can watch recorded videos on [Tumblr](https://radiumsoftware.tumblr.com/tagged/bitwig).

repo_name: keijiro/BitwigGrids


text:
#[
  Nim DLL Sideloading (a.k.a. DLL proxying) example

  Author: Marcello Salvati (@byt3bl33d3r)
]#

import os
import strformat
import winim/lean

# If you prefer to not pass the .def file via CLI during compilation uncomment the line below, and replace with actual filename
#{.passl: " mydeffile.def".}

proc NimMain() {.cdecl, importc.}

proc doMagic(lpParameter: LPVOID) : DWORD {.stdcall.} =
  var profile = getEnv("USERPROFILE")
  var f = open(fmt"{profile}\Desktop\works.txt", FileMode.fmWrite)
  f.write("It works!")
  f.close()
  MessageBox(0, "Hello!", "Nim sideloaded DLL has executed", 0)
  return 0

proc DllMain(hinstDLL: HINSTANCE, fdwReason: DWORD, lpvReserved: LPVOID) : BOOL {.stdcall, exportc, dynlib.} =
  NimMain() # You must manually import and start Nim's garbage collector if you define you're own DllMain
  case fdwReason:
    of DLL_PROCESS_ATTACH:
      var threadHandle = CreateThread(NULL, 0, doMagic, NULL, 0, NULL)
      CloseHandle(threadHandle)
    of DLL_THREAD_ATTACH:
      discard
    of DLL_THREAD_DETACH:
      discard
    of DLL_PROCESS_DETACH:
      discard
    else:
      discard

  return true

repo_language: Nim


text:
import pefile
import sys
import pathlib

dll_path = pathlib.Path(sys.argv[1])
dll = pefile.PE(dll_path)

print("EXPORTS")
for export in dll.DIRECTORY_ENTRY_EXPORT.symbols:
    if export.name:
        print(f"\t{export.name.decode()}={dll_path.stem}.{export.name.decode()} @{export.ordinal}")
else:
    print(f"\tNimMain @{export.ordinal + 2} NONAME PRIVATE")


text:
#vpath %.dll .

NIMFLAGS = -d=release -d=mingw -d=strip --opt=size --mm=orc --threads=on
#NIMFLAGS = -d=debug -d=mingw --embedsrc=on --hints=on

DLLS = $(notdir $(wildcard build/*.dll))
RANDOM = $(shell python -c 'import string,random; print("".join(random.choice(string.ascii_letters) for i in range(8)))' )
export RANDOM

.PHONY: clean
.PHONY: build

default: build

clean:
	rm -f out/*.dll
	rm -rf build/*.def

build: $(DLLS)

rebuild: clean build

%.dll: dllproxy.nim
	cp build/$*.dll out/$$RANDOM.dll
	python gen_def.py out/$$RANDOM.dll > build/$*.def
	nim c $(NIMFLAGS) --app=lib --nomain --cpu=amd64 --passl:build/$*.def --out=out/$*.dll dllproxy.nim


text:
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
#    pip-compile --generate-hashes requirements.in
#
future==0.18.2 \
    --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d
    # via pefile
pefile==2022.5.30 \
    --hash=sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b
    # via -r requirements.in


text:
# Nim DLL Sideloading

This repo allows you to easily generate Nim DLLs you can use sideloading/proxy loading.
If you're unfamiliar with what DLL sideloading is, take a gander at [this blog post](https://redteaming.co.uk/2020/07/12/dll-proxy-loading-your-favorite-c-implant/).

## How do I use this?

Put the legit DLL (or multiple DLLs) you want to proxy into the `build` directory of this repo then run `make`.

This will generate two `.dll`s in the `out` directory:
- The randomly named one is the original DLL that function calls will be proxied too
- The other will be your payload

If sideloading is successful, you should see a message box popup and/or a file written to the desktop.

To actually "weaponize" the payload, you can take a look at the [OffensiveNim](https://github.com/byt3bl33d3r/OffensiveNim) repository for ideas and ready-to-go code snippets :).

## How does it work?

- `dllproxy.nim` is the proxy DLL that will execute your payload and proxy legit function calls to the original DLL(s)
- `gen_def.py` parses the original DLL's export table and generates a [Module-definition (`.def`) file](https://learn.microsoft.com/en-us/cpp/build/reference/module-definition-dot-def-files?view=msvc-170).

The `.def` file then gets passed to the linker at compile time in order to proxy the legit function calls.

## Bonus - Hide `NimMain` from a Nim DLL's export table

See this [Microsoft article](https://learn.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170)

Notice the last line of this `.def` file:

```
EXPORTS
	GetFileVersionInfoA=ZCnerzNI.GetFileVersionInfoA @1
	GetFileVersionInfoByHandle=ZCnerzNI.GetFileVersionInfoByHandle @2
	GetFileVersionInfoExA=ZCnerzNI.GetFileVersionInfoExA @3
	GetFileVersionInfoExW=ZCnerzNI.GetFileVersionInfoExW @4
	GetFileVersionInfoSizeA=ZCnerzNI.GetFileVersionInfoSizeA @5
	GetFileVersionInfoSizeExA=ZCnerzNI.GetFileVersionInfoSizeExA @6
	GetFileVersionInfoSizeExW=ZCnerzNI.GetFileVersionInfoSizeExW @7
	GetFileVersionInfoSizeW=ZCnerzNI.GetFileVersionInfoSizeW @8
	GetFileVersionInfoW=ZCnerzNI.GetFileVersionInfoW @9
	VerFindFileA=ZCnerzNI.VerFindFileA @10
	VerFindFileW=ZCnerzNI.VerFindFileW @11
	VerInstallFileA=ZCnerzNI.VerInstallFileA @12
	VerInstallFileW=ZCnerzNI.VerInstallFileW @13
	VerLanguageNameA=ZCnerzNI.VerLanguageNameA @14
	VerLanguageNameW=ZCnerzNI.VerLanguageNameW @15
	VerQueryValueA=ZCnerzNI.VerQueryValueA @16
	VerQueryValueW=ZCnerzNI.VerQueryValueW @17
	NimMain @19 NONAME PRIVATE
```

This last line basically tells the linker to remove the name of the `NimMain` function. Compiling the DLL and passing the above `.def` file to the linker results in the following entry if we take a look at the `.dll`'s export table:

![HiddenNimMain](https://user-images.githubusercontent.com/5151193/205427811-01e4e941-f79f-40c8-b96d-eb3292200410.png)


text:
FROM mcr.microsoft.com/devcontainers/python:3.11

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends build-essential mingw-w64 binutils

USER vscode
ENV CHOOSENIM_NO_ANALYTICS 1
ENV PATH $HOME/.nimble/bin:$PATH
RUN echo 'export PATH="$HOME/.nimble/bin:$PATH"' >> ~/.profile


text:
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
	"name": "NimDllSideload",
	"build": {
		"dockerfile": "Dockerfile"
	},
	// Features to add to the dev container. More info: https://containers.dev/features.
	// "features": {},

	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	// Use 'postCreateCommand' to run commands after the container is created.
	// "postCreateCommand": "pip3 install --user -r requirements.txt",
	"postCreateCommand": "curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y && nimble -y install winim c2nim",
	"customizations": {
		"vscode": {
			"extensions": [
				"ms-python.python",
				"kosz78.nim"
			]
		}
	},

	// Configure tool-specific properties.
	// "customizations": {},

	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
	// "remoteUser": "root"

}


text:
version: 2.1

workflows:
  build-and-test:
    jobs:
      - build:
          context: dockerauth

jobs:
  build:
    docker:
      - image: cimg/python:3.10
        auth:
          username: $DOCKERHUB_USERNAME
          password: $DOCKERHUB_PASSWORD
    resource_class: small
    steps:
      - checkout
      - run: python -m pip install -r requirements.txt
      - run: python -m pytest

repo_language: Python


text:
# Contributors

List of people who have contributed either directly with merged changes or proposing changes that were adapted and integrated in the code:

- Marcin Konowalczyk / [@MarcinKonowalczyk](https://github.com/MarcinKonowalczyk)
- Dave Lewis / [@fromdavelewis](https://github.com/fromdavelewis)


text:
.PHONY: default deps base build stop shell test demo-terminal demo-image run-stats

export SERVICE_NAME := mazes-for-programmers

BUILD_ENV ?= dev

HISTORY_FILE := ~/.bash_history.$(SERVICE_NAME)

# Required for the `deps` task
SHELL := $(shell which bash)

DOCKER := $(shell command -v docker)
COMPOSE := $(shell command -v docker) compose

COMPOSE_ENV := $(COMPOSE) -f build/$(BUILD_ENV)/docker-compose.yml
COMPOSE_CMD := $(COMPOSE_ENV) run --rm $(SERVICE_NAME)

export GID := $(shell id -g)
export UID := $(shell id -u)

deps:
ifndef DOCKER
	@echo "Docker is not available. Please install docker"
	@exit 1
endif
ifndef COMPOSE
	@echo "docker-compose is not available. Please install docker-compose"
	@exit 1
endif
	@touch $(HISTORY_FILE)

base: deps
	$(COMPOSE) -f build/base/docker-compose.yml build

build: base
	$(COMPOSE_ENV) build

stop:
	$(COMPOSE_ENV) stop
	$(COMPOSE_ENV) rm -f -v

shell: build
	$(COMPOSE_CMD) /bin/bash

test: build
	set -o pipefail; \
	$(COMPOSE_CMD) pytest

demo-terminal: build
	$(COMPOSE_CMD) python3 demos/terminal_demo.py $(rows) $(cols) $(algorithm) $(exporter) $(rotations) $(pathfinding)

demo-image: build
	$(COMPOSE_CMD) python3 demos/image_demo.py $(rows) $(cols) $(algorithm) $(exporter) $(rotations) $(pathfinding) $(coloring)

demo-game-map: build
	$(COMPOSE_CMD) python3 demos/game_map_demo.py $(rows) $(cols) $(algorithm)

run-stats: build
	$(COMPOSE_CMD) python3 demos/stats_demo.py $(rows) $(cols) --tries=$(tries) --pathfinding=true


text:
Pillow==10.0.0

# test-related (but not separated for now)
mypy==1.5.0
pytest==7.4.0
flake8==6.1.0


text:
# Mazes For Programmers Python Sources

[![CircleCI](https://circleci.com/gh/Kartones/mazes-for-programmers-python-src/tree/master.svg?style=svg)](https://circleci.com/gh/Kartones/mazes-for-programmers-python-src/tree/master)

## Introduction

I'm reading the [Mazes for Programmers](http://www.mazesforprogrammers.com) book, but source code comes in Ruby and I like Python, so I decided to rewrite them as I read. And along the way add tests, both to make sure the conversion is ok and to see a more continuous way than having to write all basic stuff and an "ASCII renderer" before being able to see anything.

A small remark: Code is not a 1:1 copy of the book's. For example I built exporters instead of adding `to_s` and `to_png` methods, pathfinding is also a module that works over traversable grids (those with distances calculated), and a few other changes and extras.

## Implemented algorithms

- `AldousBroder`
- `BinaryTree`
- `HuntAndKill`
- `RecursiveBacktracker`
- `Sidewinder`
- `Wilson`

Note: This list will grow as I progress with the book.

## Implemented exporters

- `ASCIIExporter`: outputs to console
```
+---+---+---+---+---+---+
|                       |
+   +   +   +---+---+   +
|   |   |   |           |
+---+   +---+---+   +   +
|       |           |   |
+---+---+---+---+   +   +
|                   |   |
+   +---+   +   +---+   +
|   |       |   |       |
+   +   +   +   +---+   +
|   |   |   |   |       |
+---+---+---+---+---+---+
```

- `PNGExporter`: outputs to a PNG file on the project root folder (filename will be current datetime)

![](doc/sample_binary_tree.png)

Depending on the pathfinding and coloring flags combination can draw the colored solution path or a "distance-colored map" from the center.

![](doc/sample_colored_pathfinding.png)

![](doc/sample_colored_maze.png)

- `UnicodeExporter`: outputs to console (prettier than raw ASCII)
```
┏━━━━━━━━━━━━━━━━━━━━━━━┓
┃                       ┃
┃           ┏━━━━━━━    ┃
┃   ┃   ┃   ┃           ┃
┣━━━┛   ┣━━━┻━━━        ┃
┃       ┃           ┃   ┃
┣━━━━━━━┻━━━━━━━    ┃   ┃
┃                   ┃   ┃
┃   ┏━━━        ┏━━━┛   ┃
┃   ┃       ┃   ┃       ┃
┃   ┃       ┃   ┣━━━    ┃
┃   ┃   ┃   ┃   ┃       ┃
┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┛
```

#### Wolfenstein 3D Exporter

Special section for my small hack that I'm fond of, a "different" exporter:

- `Wolf3DExporter`: outputs to a LEV file, to be used from [NMAP](http://toastytech.com/files/nmap.html) tool to import as a [Wolfenstein 3D](https://en.wikipedia.org/wiki/Wolfenstein_3D) map. Level exit is at the pathfinding end (most distant cell from starting position), and on each dead-end there is an enemy soldier (when you attack one all will start to move around the map). Also outputs a PNG file with the maze solution. **To be used with** `game_map_demo.py` **demo runner**.

![](doc/nmap_sample_map.png)
![](doc/nmap_sample_helper_map.png)

Sample small map playthrough (start at blue, end at red, enemy on deadend at white zone):

![hint map](doc/wolf3d_sample_hintmap.png)
![Wolfenstein3D playthrough](doc/wolf3d_sample.gif)

## Implemented pathfinding algorithms

- `Dijkstra`: Uses cell distances to calculate maze solution. The actual "core" logic lives at `Distances` base class.
- `LongestPath`: Calculates "a longest path" of the maze. There can be many as it selects a cell as starting point and could be other longer ones.

## Setup

You just need to have installed `make`, `docker` and `docker-compose`.

## Execute

Easiest way is to run the corresponding `demo-xxxx` make command, e.g.:
```bash
make demo-terminal rows=<value> cols=<value> algorithm=<value>

# example
make demo-terminal rows=10 cols=10 algorithm=AldousBroder

# examples of how to send optional params through make
make demo-terminal rows=15 cols=15 algorithm=AldousBroder pathfinding=-p=true
make demo-image rows=25 cols=25 algorithm=AldousBroder pathfinding=-p=true coloring=-c=true
```
Run without arguments to see all available options

Available make commands that run algorithms:
- `demo-terminal`
- `demo-image`
- `demo-game-map`
- `run-stats`

An alternative is to open a shell into the container and then run from the inside any demo:

```bash
# start and enter into container
make shell

# then run desired demo
python3 demos/<filename>
```

Available demo runners:
- `terminal_demo.py`
- `game_map_demo.py`
- `image_demo.py`
- `stats_demo.py`

And read the instructions of required and optional parameters (run without arguments and it will explain usage).

Usually you have to choose a desired grid size (in number of rows and columns) and the algorithm to use. Optionally you can select a few other parameters.

Stats demo runs all available algorithms a certain number of times and gathers statistics and metrics, careful with launching it with big mazes as might take a while.
Sample output:
```
PYTHONPATH=. python3 demos/stats_demo.py 25 25 --pathfinding
Rows: 25
columns: 25
Total cells: 625
Runs per algorithm: 100
Pathfinding: True
> running AldousBroder
> running BinaryTree
> running HuntAndKill
> running RecursiveBacktracker
> running Sidewinder
> running Wilson

Average dead-ends (deadends/total-cells, sorted by % desc):
           AldousBroder: 182/625 (29.12%)
                 Wilson: 181/625 (28.93%)
             Sidewinder: 171/625 (27.29%)
             BinaryTree: 156/625 (24.97%)
   RecursiveBacktracker: 065/625 (10.47%)
            HuntAndKill: 061/625 (9.73%)

Generation speed benchmark (seconds, sorted by average desc):
                 Wilson: avg: 0.641611 min: 0.235594 max: 2.173624
            HuntAndKill: avg: 0.078919 min: 0.059095 max: 0.101278
           AldousBroder: avg: 0.038898 min: 0.015946 max: 0.180922
   RecursiveBacktracker: avg: 0.005492 min: 0.005383 max: 0.006105
             BinaryTree: avg: 0.002130 min: 0.002074 max: 0.002359
             Sidewinder: avg: 0.002105 min: 0.002039 max: 0.002320

Pathfinding speed benchmark (seconds, sorted by average desc):
           AldousBroder: avg: 0.014295 min: 0.011494 max: 0.035487
   RecursiveBacktracker: avg: 0.012775 min: 0.012238 max: 0.014378
            HuntAndKill: avg: 0.012100 min: 0.011589 max: 0.013740
                 Wilson: avg: 0.011712 min: 0.011262 max: 0.013362
             Sidewinder: avg: 0.011641 min: 0.011314 max: 0.013308
             BinaryTree: avg: 0.011561 min: 0.011267 max: 0.013016

```

## Testing

Note: Runs also linter tests, to conform with both `mypy` and `flake8`.

```bash
make test
```

## Roadmap & TODOs

- Of course finish the book and implement all main code and algorithms
- Check in depth mypy doc to see why all the issues with Union, probably I'm doing something wrong and doesn't detects properly hierarchies, etc.
- Check to improve Wolf3DExporter drawing of tiles so I can have bigger maps (each cell now uses 2x2 map tiles)
- Implement more pathfinders -> (e.g. recursive backtracking as a maze solving algorithm)


text:
from random import choice, randint
from typing import cast

from algorithms.base_algorithm import Algorithm
from base.grid import Grid
from base.cell import Cell

class Sidewinder(Algorithm):
    """
    A sidewinder visits each cell in the grid and chooses to carve a passage either north or east
    (similar to Binary Tree), but running row by row.
    Causes topmost row to always be a straight line.
    """

    def on(self, grid: Grid) -> None:
        for row in grid.each_row():
            run = []
            for cell in row:

                run.append(cell)
                at_eastern_boundary = cell.east is None
                at_northen_boundary = cell.north is None
                should_close_out = at_eastern_boundary or (not at_northen_boundary and randint(0, 1) == 0)
                if should_close_out:
                    member = choice(run)
                    if member.north:
                        member += member.north
                    run.clear()
                else:
                    cell += cast(Cell, cell.east)