Sunday, January 23, 2022

Convert image to printable ASCII with Python

我上大学那时还没有喷墨和激光打印机,只有9针/16针的针式打印机,只能打印ASCII字符,不能打印图像。有很酷的程序把图像转换成ASCII符。Google可以找到很多这类程序,如:

https://github.com/qeesung/image2ascii

https://github.com/uvipen/ASCII-generator

https://github.com/LazoCoder/Image-To-ASCII

这里share一个我写的比较简单的程序 (font file can be downloaded from fonts.google.com: hhttps://fonts.google.com/specimen/Courier+Prime?query=courier):

import tkinter
from tkinter import filedialog
from PIL import Image, ImageFont

def create_char_map():
characters = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
weights = []

font_name = "Courier_Prime/CourierPrime-Regular.ttf"
font_size = 16 # px
font_color = "#000000" # HEX Black
font = ImageFont.truetype(font_name, font_size)
max_weight = 0

# Loop through the characters needed and save to desired location
for character in characters:
# Get text size of character
width, height = font.getsize(character)
pix = font.getmask(character)
weight = 0
for y in range(len(pix)//width): # the actual row might be smaller than hegith
for x in range(width):
weight += pix[x+width*y]
weights.append(weight)
if weight>max_weight: max_weight = weight
# uniform the weight to 0~256*3-1
char_map = {767-int(w*767/max_weight):ch for (w, ch) in zip(weights, characters)}
# for revert image: char_map = {int(w*767/max_weight):ch for (w, ch) in zip(weights, characters)}
return char_map

def get_pixmap(path):
im = Image.open(path)
print('Original: ' + str(im.size))
size = 320
if im.size[0] > size:
resized_image = im.resize((size, int(size * im.size[1] / im.size[0])))
pixmap = resized_image.load()
pixmap_size = resized_image.size
print('Resized: ' + str(pixmap_size))
else:
pixmap = im.load()
pixmap_size = im.size
return pixmap, pixmap_size
 
def getchar(rgb_sum):
for i in range(1, len(char_keys)):
if rgb_sum<char_keys[i]:
if char_keys[i]+char_keys[i-1]>rgb_sum*2: i=i-1
break
return char_map[char_keys[i]]

def main():
#return
root = tkinter.Tk()
root.withdraw()
fin=filedialog.askopenfilename()
if(fin):
pixmap, pixmap_size = get_pixmap(fin)
file = open('output.txt', 'w')
for y in range(0, pixmap_size[1]-1, 2):
line=''
for x in range(pixmap_size[0]):
# merge two line into one printed line to keep a better ratio
rgb_sum = (pixmap[x, y][0] + pixmap[x, y][1] + pixmap[x, y][2] +
 pixmap[x, y+1][0] + pixmap[x, y+1][1] + pixmap[x, y+1][2])>>1
line+=getchar(rgb_sum)
file.write(line+'\n')
file.close()

if __name__=="__main__":
char_map = create_char_map()
char_keys = sorted(char_map)
main()

Tuesday, January 11, 2022

教小朋友用Python编Minecraft server插件

前面在教小朋友编程之坦克大战 1提到David Whale, Martin O'Hanlon写的《Adventures in Minecraft》,我觉得有必要提供一些细节,特别是对喜欢玩Minecraft的小朋友,根据作者的讲述,他们在学校的实验很成功,有的学生开发出不错的程序。

这本书的官网是https://www.wiley.com/en-us/Adventures+in+Minecraft-p-9781118946916


Amazon等也都有卖。

网上可以很容易找到这本书的电子版,我不确定是否有版权问题,所以不会在这里列出link招惹不必要的麻烦。开发的Python程序是要运行在Minecraft server端。为此,需要到https://adventuresinminecraft.github.io 下载Minecraft Starter Kits。这个github repo提供了Windows、MacOS和Raspberry Pi的Start Kit, for Minecraft 1.12, 虽然不是最新版Minecraft,不过不影响趣味性。这个网站还提供如何自己创建定制的Start Kits,当然也提供了书中全部程序的下载。

网上可以找到很多的学习Python的资料,如Python Crash Course。https://www.pdfdrive.com/python-crash-course-a-hands-on-project-based-introduction-to-programming-e190067998.html提供很多ebook下载。

https://github.com/everestwitman/Pygame-Checkers/blob/master/checkers.py是一个checker board game程序,只有一个500行的Python code(算上注释空行)外加一个board的bmp file,简单易懂。

Sunday, January 2, 2022

奇妙code

 十几年前一guru同事在离职email里分享了一个99瓶啤酒的perl程序,target是打印:

99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.

98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.

....

2 bottles of beer on the wall, 2 bottles of beer.
Take one down and pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.

那时我还没听说过Google。最近一查,发现有专门的网站:http://99-bottles-of-beer.net

比如Perl code: http://99-bottles-of-beer.net/download/658

$a=
        "cpuu
       \bmft p
       \bg cff
       \bs";$b
       ="po ui
       \bf xbm
      \bm";$c="
      Ypv ublf p
     \bof epxo qb
   \btt ju bspvoe";
  $a =~ s/\n//;$a =~
  s/\s+/ /g; $b   =~
  s/\n// ;  $b    =~
  s/\s+/ /g;$c    =~
  s/\n// ;  $c    =~
  s/\s+/ /g;$a    =~
  y/b-z/a-z/;$b   =~
  tr/b-z/a-z/;$c  =~
  tr/b-z/a-z/ ; for(
  $d=100;$d>0;$d--){
  print"$d $a $b $d"
  ;print" $a,\n$c, "
  ;print($d-1);print
  " $a $b.\n";} $x =
  "cjc"; $y="dobbz";
  $z="com";print"\n"
  ;print "- $x\@$y."
   ;print"$z \n\n";

 http://99-bottles-of-beer.net/download/737 (可能需要旧Perl比如5.19之前的,不然会得到错误:Eval-group not allowed at runtime, use re 'eval' in regex):

    ''=~(        '(?{'        .('`'        |'%')        .('['        ^'-')
    .('`'        |'!')        .('`'        |',')        .'"'.        '\\$'
    .'=='        .('['        ^'+')        .('`'        |'/')        .('['
    ^'+')        .'||'        .(';'        &'=')        .(';'        &'=')
    .';-'        .'-'.        '\\$'        .'=;'        .('['        ^'(')
    .('['        ^'.')        .('`'        |'"')        .('!'        ^'+')
   .'_\\{'      .'(\\$'      .';=('.      '\\$=|'      ."\|".(      '`'^'.'
  ).(('`')|    '/').').'    .'\\"'.+(    '{'^'[').    ('`'|'"')    .('`'|'/'
 ).('['^'/')  .('['^'/').  ('`'|',').(  '`'|('%')).  '\\".\\"'.(  '['^('(')).
 '\\"'.('['^  '#').'!!--'  .'\\$=.\\"'  .('{'^'[').  ('`'|'/').(  '`'|"\&").(
 '{'^"\[").(  '`'|"\"").(  '`'|"\%").(  '`'|"\%").(  '['^(')')).  '\\").\\"'.
 ('{'^'[').(  '`'|"\/").(  '`'|"\.").(  '{'^"\[").(  '['^"\/").(  '`'|"\(").(
 '`'|"\%").(  '{'^"\[").(  '['^"\,").(  '`'|"\!").(  '`'|"\,").(  '`'|(',')).
 '\\"\\}'.+(  '['^"\+").(  '['^"\)").(  '`'|"\)").(  '`'|"\.").(  '['^('/')).
 '+_,\\",'.(  '{'^('[')).  ('\\$;!').(  '!'^"\+").(  '{'^"\/").(  '`'|"\!").(
 '`'|"\+").(  '`'|"\%").(  '{'^"\[").(  '`'|"\/").(  '`'|"\.").(  '`'|"\%").(
 '{'^"\[").(  '`'|"\$").(  '`'|"\/").(  '['^"\,").(  '`'|('.')).  ','.(('{')^
 '[').("\["^  '+').("\`"|  '!').("\["^  '(').("\["^  '(').("\{"^  '[').("\`"|
 ')').("\["^  '/').("\{"^  '[').("\`"|  '!').("\["^  ')').("\`"|  '/').("\["^
 '.').("\`"|  '.').("\`"|  '$')."\,".(  '!'^('+')).  '\\",_,\\"'  .'!'.("\!"^
 '+').("\!"^  '+').'\\"'.  ('['^',').(  '`'|"\(").(  '`'|"\)").(  '`'|"\,").(
 '`'|('%')).  '++\\$="})'  );$:=('.')^  '~';$~='@'|  '(';$^=')'^  '[';$/='`';

有意思的是这个程序可能是用Acme::EyeDrops从一个简单的Perl程序转换来的:

use Acme::EyeDrops qw(sightly get_eye_string hjoin_shapes);
my $ninety_nine = <<'BURP';
$==pop||99;--$=;sub
_{($;=($=||No)." bottle"."s"x!!--$=." of beer")." on the wall"}
print+_,", $;!
Take one down, pass it around,
",_,"!

"while++$=
BURP
chop($ninety_nine); $ninety_nine =~ s/\nprint/print/;
print sightly( { Regex         => 1,
                 Compact       => 1,
                 ShapeString   => hjoin_shapes(2,
                                  (get_eye_string('bottle2'))x6),
                  SourceString  => $ninety_nine } );

 关于99个啤酒瓶编程的相关网站很多,如:

https://codegolf.stackexchange.com/questions/2/99-bottles-of-beer

https://titanwolf.org/Network/Articles/Article?AID=01592803-8636-4fcd-ac1a-3cc92050a741

之所以想起旧事,是因为最近看了Dylan Beattie的<<The Art of Code>>

里面提到Oscar Toledo G. 2010年用1kB Javascript实现的mini chess:

<html>
<head>
<title>1K Javascript Tiny Chess by &Oacute;scar Toledo G. &copy;2010</title>
</head>
<body>
<script>
for(B=i=y=u=b=i=5-5,x=10,I=[],l=[];B++<304;I[B-1]=B%x?B/x%x<2|B%x<2?7:B/x&4?0:l[i++]="ECDFBDCEAAAAAAAAIIIIIIIIMKLNJLKM@G@TSb~?A6J57IKJT576,+-48HLSUmgukgg OJNMLK  IDHGFE".charCodeAt(y++)-64:7);function X(c,h,e,s){c^=8;for(var o,S,C,A,R,T,G,d=e&&X(c,0)>1e4,n,N=-1e8,O=20,K=78-h<<9;++O<99;)if((o=I[T=O])&&(G=o^c)<7){A=G--&2?8:4;C=o-9?l[61+G]:49;do if(!(R=I[T+=l[C]])&&!!G|A<3||(R+1^c)>9&&G|A>2){if(!(R-2&7))return K;n=G|(c?T>29:T<91)?o:6^c;S=(R&&l[R&7|32]*2-h-G)+(n-o?110:!G&&(A<2)+1);if(e>h||1<e&e==h&&S>2|d){I[T]=n;I[O]=0;S-=X(c,h+1,e,S-N);if(!(h||e-1|B-O|T-b|S<-1e4))return W(),c&&setTimeout("X(8,0,2),X(8,0,1)",75);I[O]=o;I[T]=R}if(S>N||!h&S==N&&Math.random()<.5)if(N=S,e>1)if(h?s-S<0:(B=O,b=T,0))break}while(!R&G>2||(T=O,(G||A>2|(c?O>78:O<41)&!R)&&++C*--A))}return-K+768<N|d&&N}function W(){i="<table>";for(u=18;u<99;document.body.innerHTML=i+=++u%x-9?"<th width=60 height=60 onclick='I[b="+u+"]>8?W():X(0,0,1)'style='font-size:50px'bgcolor=#"+(u-B?u*.9&1||9:"d")+"0f0e0>&#"+(I[u]?9808+l[67+I[u]]:160):u++&&"<tr>")B=b}W()
</script>
</body>
</html>
用任何网页浏览器打开它就会发现它是一个完全可运行的国际象棋游戏。Oscar Toledo G还做了好几个不同版本的c/java类似程序,参见:https://nanochess.org/chess.html
Dylan Beattie在<<The Art of Code>>中提到另一个Flappy Bird code:

                  #define P(a,b,c) a##b##c
                #include/*++**++*/<curses.h>
              int         c,h,            v,x,y,s,                i,b; int
            main            () {            initscr(              ); P(cb,
          rea,                k)()                ;///
        P(n,                  oec,                ho)(
       )/*     */             ;for            (curs_set(0); s=        x=COLS/2
      ; P(    flu,            shi,          np)()){ timeout(y=c=      v=0);///
      P(c,    lea,            r)()          ;for              (P (
      mva,     d,             dstr        )(2,                3+x,
      G) ;                  ; P(        usl,                  eep,    )(U)){//
       P(m,               vad,         dstr                   )( y    >>8,x,//
    "    "); for(i=LINES; /*           */ i                   -->0
  ; mvinsch(i,0,0>(~c|i-h-H             &h-i                  )?' '
:(i-                      h|h-            i+H)            <0?'|'      :'=' ));
if((                       i=( y            +=v=        getch(        )>0?I:v+
  A)>>8)>=LINES||mvinch(i*=   0<i,            x)!=' '||' '
  !=mvinch(i,3+x))break/*&%   &*/;              mvaddstr(y
    >>8,                   x,0>v                      ?F:B        ); i=--s
    /-W;                  P(m,                        vpr,        intw)(0,
     COLS-9," %u/%u ",(0<i)*                  i,b=b<i?i:
      b); refresh(); if(++                    c==D){ c
                        -=W; h=rand()%(LINES-H-6
                          )+2; } } flash(); }}

是个完全可编译可运行的Flappy bird程序(depends on libcurses-devel, 需要下载makefile, 里边定义了游戏参数)。实际上,上面的程序是The International Obfuscated C Code Contest比赛2015年的优胜者之一。从1984年到2020年已经举办27届,优胜程序列在这里:https://www.ioccc.org/years-spoiler.html,其中的每一个程序都一定会让每个C程序员惊叹不已。上面提到的Oscar Toledo G.也有5个程序获奖。

下面的是Youtube video关于Andy Sloane的Donut-shaped C code生成3D spinning donuts

  

Andy的C code source:

             k;double sin()
         ,cos();main(){float A=
       0,B=0,i,j,z[1760];char b[
     1760];printf("\x1b[2J");for(;;
  ){memset(b,32,1760);memset(z,0,7040)
  ;for(j=0;6.28>j;j+=0.07)for(i=0;6.28
 >i;i+=0.02){float c=sin(i),d=cos(j),e=
 sin(A),f=sin(j),g=cos(A),h=d+2,D=1/(c*
 h*e+f*g+5),l=cos      (i),m=cos(B),n=s\
in(B),t=c*h*g-f*        e;int x=40+30*D*
(l*h*m-t*n),y=            12+15*D*(l*h*n
+t*m),o=x+80*y,          N=8*((f*e-c*d*g
 )*m-c*d*e-f*g-l        *d*n);if(22>y&&
 y>0&&x>0&&80>x&&D>z[o]){z[o]=D;;;b[o]=
 ".,-~:;=!*#$@"[N>0?N:0];}}/*#****!!-*/
  printf("\x1b[H");for(k=0;1761>k;k++)
   putchar(k%80?b[k]:10);A+=0.04;B+=
     0.02;}}/*****####*******!!=;:~
       ~::==!!!**********!!!==::-
         .,~~;;;========;;;:~-.
             ..,--------,*/

 编译:

gcc -o donut donut.c -lm

Updated on Oct 11, 2023: Just saw a Wolfenstein implementation with 600 line Gawk code, a 3D game in Text mode, though will feel dizzy when playing. Reference: https://hackaday.com/2016/01/15/wolfenstein-in-600-lines-of-code/:

sudo apt-get install gawk
git clone https://github.com/TheMozg/awk-raycaster.git
cd awk-raycaster/
gawk -f awkaster.awk