Rita en cirkel på Commodore 64

Det finns ett meme som visar hur omständligt det är att programmera BASIC på Commodore 64 (C64). Bilden visar den kod som krävs för att rita en cirkel på skärmen på en C64, jämfört med den kod som krävs för att rita en cirkel på skärmen på Sinclair Spectrum.

Om du skummar igenom koden i bilderna, så noterar du säkert två saker: C64-versionen är onödigt pratig och Spectrum-versionen lider lite av att skärmupplösningen är aningen lägre än C64-versionen (256*192 pixlar jämfört med 320*200 pixlar).

Denna kod är galet långsam, men det är det kortaste jag har lyckats åstadkomma för att rita en cirkel på C64:

10 POKE 56,32:POKE 52,32
20 CLR:POKE 53272,25
30 POKE 53265,PEEK(53265) OR 32
40 PRINT "{CLEAR}"
50 POKE 88,0:POKE 89,63
60 POKE 113,64:POKE 114,31
70 POKE 12,1:SYS 45760
80 FOR A=1 TO 7.3 STEP 0.01
90 X=COS(A)*20
100 Y=SIN(A)*22
110 XX=159+4*X:YY=99+3*Y
120 ROW=INT(YY/8):CH=INT(XX/8)
130 LINE=YY AND 7:BIT=7-(XX AND 7)
140 BYTE=8192+ROW*320+CH*8+LINE
150 POKE BYTE,PEEK(BYTE) OR 2^BIT
160 NEXT
170 GOTO 170

Detta är resultatet:

Jag skulle tro att orsaken till denna skillnad är att C64 egentligen är en spelkonsol, utklädd till dator. C64 var vid lansering utrustad med extra allt inom ljud och grafik, och för att spara minne slängde man in en äldre version av Commodore BASIC, nämligen 2.0 (second release). Samma som VIC-20, men ett par versioner lägre än den PET. Den enda anledningen till att C64 faktiskt hade en BASIC och ett tangentbord var att man behövde ett säljargument, som tyvärr inte är mer sofistikerat än Annika Strandhälls försvar för iPads i skolan – man blir ett datageni! En skillnad är möjligtvis att det var lägre hängande frukt att lära sig programmering på en hemdator 1982 än det är att lära sig programmering på en kontemporär Facebook-maskin från Apple. Sinclair Spectrum (också 1982) däremot, var en ytterst begränsad dator. Att de barn som fick den skulle lära sig programmering, var på allvar ett viktigt skäl att köpa datorn. Den behövde ha en seriös BASIC! (Jag själv lärde mig BASIC på föregångaren Sinclair ZX81.)

Commodore 128 hade all den kapacitet ljud- och grafikkapacitet som C64 hade, men till skillnad från C64 hade den även kommandon och verktyg som lät användaren dra nytta av den kapaciteten, utan att behöva programmera maskinkod. 128-användaren fick tillgång till en bra BASIC och en drös inbyggda verktyg. Programmet som ritar en cirkel på Commodore 128 är inte helt olikt Spectrum-programmet i memen ovan:

10 GRAPHIC 1,1
20 CIRCLE 1,159,99,60,60

Och även om vi inte hade CIRCLE-kommandot, så blir koden betydligt kortare än C64-versionen, eftersom vi kan ändra grafikläge och sätta individuella bildelement direkt från BASIC. Så uppgiften är inte särskilt svår ändå. Man betalar för lyxen genom högre minneskrav, men denna lyx ansåg man inte att målgruppen för C64 var intresserad av.

Vad är vinsten att skriva i maskinkod?

För ett par veckor sedan skrev jag en kort text om hur man optimerar prestandan i Commodore BASIC 2.0 second release. Det gjorde att jag fick frågan om hur mycket man tjänar på att programmera maskinkod istället för BASIC. I mitt exempel drog jag ner exekveringstiden från 1522 jiffys (sextiondels sekunder) till 686 jiffys, genom att välja rätt kommandon och rätt strategi i en konstruerad uppgift: Spara tre värden i tre olika minnesadresser, 1000 gånger.

Uppgiften var att låta en VIC-20 skriva värdet 40 till adress 828, värdet 41 till adress 829 och värdet 42 till adress 830, ett tusen gånger, vilket alltså går att göra på 686 jiffys. Strategin för god prestanda måste utgå ifrån att realtidstolkning tar tid. Men så fick jag frågan om hur lång tid det tar för en VIC-20 att lösa samma uppgift om man skriver programmet i maskinkod. Jag gissade på nolltid, men rätt svar är 2 jiffys, alltså en trettiondels sekund.

Hur ser ett program, enligt ovan, konverterat till maskinkod ut? Stegenser ut som följer:

  1. Initiera variabeln som håller räkning på antal körningar. Detta kräver två bytes, eftersom 1000 är ett 16-bitarstal, medan VIC-20 är en 8-bitarsdator.
  2. Utför uppgiften i fråga. Skriv tre värden till tre minnesadresser.
  3. Räkna upp variabeln.
  4. Upprepa om uppgiften körts färre gånger än 1000.
  5. Avsluta.

Jag väljer att lagra variabeln på adress 880-881 och lägga programmet på adress 831. På det viset ryms programmet mellan de tre adresser som är föremål för uppgiften (828-830) och variabeln som håller reda på antalet upprepningar (1000 stycken) på adress 880-881.

1. Assembler-koden för att initiera variabeln som håller räkning på antal körningar ser ut så här (adress 831):

LDA #0
STA 880
LDA #0
STA 881

2. Uppgiften är att skriva värdet 40 till adress 828, värdet 41 till adress 829 och värdet 42 till adress 830 (adress 841):

LDA #40
STA 828
LDA #41
STA 829
LDA #42
STA 830

3. Därefter ska variabeln som håller reda på antal skrivningar till minnet räknas upp (adress 856):

INC 880
BNE 864 ;(som är adressen till beslutet att iterera)
INC 881

4. Om uppgiften utförts färre än 1000 gånger, ska uppgiften upprepas (adress 864):

LDA  880
CMP  #232
BNE  841
LDA  881
CMP  #3
BNE  841

5. Avsluta.

RTS

Översatt till maskinkod, kan vi konstatera att vi har:

  1. 169 0 141 112 3 169 141 113 3
  2. 169 40 141 60 3 169 41 169 41 141 61 3 169 42 141 62 3
  3. 238 112 3 208 3 238 113 3
  4. 173 112 3 201 232 208 226 173 113 3 201 3 208 189
  5. 96

Om vi skriver en BASIC-loader för detta, och sedan tar tid på exekveringen, så ser koden ut så här:

10 FOR N=0 TO 47
20 READ V
30 POKE 831+N,V
40 NEXT
50 S=TI
60 SYS 831
70 PRINT TI-S
80 DATA 169,0,141,112,3,169,0,141
90 DATA 113,3,169,40,141,60,3,169
100 DATA 41,141,61,3,169,42,141,62
110 DATA 3,238,112,3,208,3,238,113
120 DATA 3,173,112,3,201,232,208,226
130 DATA 173,113,3,201,3,208,219,96

Och mycket riktigt har vi nu utfört samma uppgift på 2 jiffys istället för 686 jiffys. Maskinkod är alltså inte så lite snabbare än BASIC på VIC-20.

Prestanda i Commodore BASIC 2.0 second release

Om du skriver ett program i Commodore BASIC för C64 (Commodore 64) eller (som i mitt fall) VIC-20 är prestanda viktigt, speciellt om det är ett spelprogram som du bygger. BASIC-tolken är ganska slö, och det faktum att både C64 och VIC-20 interpreterar BASIC-programmet medan det körs, gör att det är vissa specifika faktorer som man behöver ta hänsyn till.

Två faktorer som påverkar prestandan ganska mycket – hur man skriver minne och hur man itererar – är två faktorer som ofta används vid just spelprogrammering. Men även om du blir expert på BASIC, kommer du inte kunna skriva spel som är lika avancerade som t.ex. Wizball, The Great Giana Sisters eller Turrican i BASIC, men du kommer kunna skapa hyfsat bra spel. T.ex. är Pirates! programmerat i BASIC (Sid Meier), precis som Artworks Strip Poker, så man kommer ganska långt om man lär sig språket ordentligt.

Jag kör mina prestandaexempel på min VIC-20. En VIC-20 är aningen snabbare än en C64, ändå är allt som påstås här sant även på C64 även om siffrorna blir lite annorlunda.

För att mäta prestanda kan man spara värdet i TIME ( eller TI) som ger systemklockans värde, utföra den operation man vill mäta, och sedan titta på differensen mellan det nya värdet i TIME och det värde man lagrade. Då vet man hur många jiffys (alltså hur många sextiondels sekunder) operationen har kostat.

Följande program skriver till fyra minnesadresser i en evighetsloop. Programmet räknar dessutom upp en variabel till 1000 och bryter när den når 1000. Det vi testar är hur snabb en iteration är, hur lång tid det tar att skriva till minnet med POKE, en aritmetisk beräkning och ett test. Så här ser källkoden ut:

10 PRINT "HASTIGHETSTEST VIC-20"
20 S=TI
30 C=0
40 POKE 828,40
50 POKE 829,41
60 POKE 830,42
70 C=C+1
80 IF C < 1000 THEN 40
90 PRINT TI-S

Resultatet, som bilden visar är 1522 jiffys (eller drygt 25 sekunder). Koden ser inte helt orimlig ut, men det är inte särskilt imponerande, så hur kan man optimera detta program?

Först är GOTO felanvänt. GOTO står inte utskrivet, men rad 80 avslutas med …THEN 40, vilket underförstått betyder …THEN GOTO 40. Syftet med GOTO är att skapa hopp i program för att förgrena logiken. Under vissa villkor ska viss kod köras, annars en annan kod. Men här används GOTO för att iterera. Det faktum att Commodore BASIC är ett interpreterande språk gör detta till ett dåligt val. Varje gång programmet ska hoppa till ett radnummer, måste radnumret tolkas och hittas. Genom att skapa iterationer med FOR, kan iterationens start och slut återanvändas under varje upprepning.

10 PRINT "HASTIGHETSTEST VIC-20"
20 S=TI
30 FOR A=0 TO 999
40 POKE 828,40
50 POKE 829,41
60 POKE 830,42
70 NEXT
80 PRINT TI-S

Nu klarar vi att utföra samma logik på 1094 jiffys (18 sekunder). Ännu trevligare blir det om vi använder oss av variabler för att lagra minnesadresser. Då behöver interpretatorn bara förstå vilken adress som avses en gång, och kan sedan återanvända det. Här kräver vi två aritmetiska beräkningar för att åstadkomma sak, men är redan nere på 835 jiffys:

Och kostar vi på oss att använda tre variabler istället för en variabel och två aritmetiska beräkningar, så hyvlar vi bort ytterligare lite overhead och landar på 686 jiffys.

10 PRINT "HASTIGHETSTEST VIC-20"
20 S=TI
30 X=828
40 Y=829
50 Z=830
60 FOR A=0 TO 999
70 POKE X,40
80 POKE Y,41
90 POKE Z,42
100 NEXT
110 PRINT TI-S

Nu har vi gått från 25 sekunder till 12 sekunder, vilket är en femtiotvåprocentig förbättring. I praktiken kan man inte optimera programmen så hårt, för man skulle få alldeles för många variabler att hålla reda på. Men när man skriver prestandakritiska program (t.ex. spelprogram) i ett långsamt språk (t.ex. Commodore BASIC) är det viktigt att veta vad man kan göra och vad man absolut ska undvika.

Jag har skrivit en bok om just Commodore BASIC på C64 och VIC-20, som bl.a. tar upp detta, som kan laddas hem helt gratis som PDF eller EPUB.

Uppdatering: Vad är vinsten att skriva i maskinkod?

Två “nya” C64-demos från 1986-87

Jag har preserverat ett antal Commodore 64-disketter från min ungdom. Alla disketter med ännu ej preserverat material gick tyvärr inte att rädda, men några små guldkorn har jag fått tag i, som t.ex. en disk med följande fillista. Antal block inom parentes.

CCCP SMESSEL IX (101)
THE REAL DEMO! (79)
LIGHTS FANTASTIC (74)
MUSIC BOX III (60)
ESC DEMO II/FBR (50)
IBM MUSIC XVII (49)
CHICKEN FARM (35)
CAS DEMO #1 (32)
DAS BOOT -TRON- (29)
EYE TO EYE/FBR (21)
SOMEWHERE’S TIME (14)
TIME TRAVELLER (12)

CCCP SMESSEL IX av CCCP år 1987 är preserverat sedan tidigare. Filen på min disk fungerar inte, men demot var en textskärm med någon rippad musik följt av en dubbel borderscroller med någon Sound Tracker-implementation av Axel F (Harold Faltermeyer), och avslutas med en stor scroller.

THE REAL DEMO! av CCCP år 1987 är också preserverat sedan tidigare. Den inleds med en fullskärmsbild förställande en kinesisk ansiktsmask och bjuder sedan på två ytterligare delar utan originalmusik.

LIGHTS FANTASTIC (titelbilden) har jag inte sett preserverat någonstans, men du kan ladda ner den här. Du lyssnar på musik och tittar på mönster som ritas med linjer på skärmen. Du kan styra linjerna lite, och du kan växla låt. Det påminner om demot Tubular Bells.

MUSIC BOX III är rippad musik och lite specialeffekter av Thunderbolt Cracking Crew.

ESC DEMO II/FBR spelar en cover på White Wedding (Billy Idol) som tar tvärstopp efter några minuter, och visar bild rippad från Fist II. Tror inte att den finns preserverad sedan tidigare, men den kan laddas ner här.

IBM MUSIC XVII av The Electronic Knights år 1987 är en låt byggd på trummorna från trummaskinen Funky Drummer.

CHICKEN FARM (We Music, 1986) är en bild och en scrolltext upp-och-ned, med musik av David Whittaker.

CAS DEMO #1 (Cas, 1986) är en bild och en stor scrolltext i undre bordern, utan musik.

DAS BOOT -TRON- av Tron (okänt år) är en bild, en låt och en teckenbaserad scrolltext.

EYE TO EYE/FBR av AM Twelve år 1986 är en ganska rolig sprite-animation där två ögon åker runt på skärmen.

Både SOMWHERE’S TIME (Rubard) och TIME TRAVELLER (Skuzz, 1987) är två stillbilder, där den senaste är en ganska snygg pixlad Eddie the Head.

Jag hoppas att detta innebär att LIGHTS FANTASTIC och ESC DEMO II/FBR blir bevarade till eftervärlden.

Samantha Fox special double-groove multi-play pressing

Henrik Andersson har donerat maxisingeln “I surrender (to the spirit of the night)” med Samantha Fox till mig. En 12″-singel med skivnummer FOXY T6. Det som gör denna speciell anges på framsidan: Special double-groove multi-play pressing.

Det är inte helt självklart vad detta betyder, eller ens att det bara är sida två som avses. Det du kommer att märka när du spelar b-sidan av skivan, är att det ser ut som att den snurrar 90 varv per minut, men singlar (som denna) ska spelas upp i 45 varv per minut.

En vanlig LP spelas i 33,3 varv per minut, vilket innebär att det tar knappt två sekunder för nålen att flytta sig från det yttersta varvet till näst yttersta varvet. En singel snurrar 45 varv per minut, vilket innebär att samma förflyttning endast tar 1,3 sekunder för nålen att flytta sig ett varv in.

A-sidan, som är den utökade mixen av “I surrender (to the spirit of the night)”, fungerar som skivor gör mest. Ljudimpulsen ligger lagrad i ett spiralformat spår som rör sig in mot mitten. Oavsett var du lägger på nålen, kommer spiralen att fånga den, och leda den in till mitten. Detta är det normala:

Det special double-groove multi-play pressing betyder, är att det outnyttjade området på en vanlig 45-varvare har, är använt till ett spår till, innehållande samma information. Bilden visar principen. Beroende på var (eller när, eftersom armens position är statisk) du lägger på nålen, kommer antingen det ena (gröna) eller andra (röda) spåret att fånga armen och leda den till mitten. I praktiken är det slumpen som avgör.

Anledningen till att man vill ha det så här, är att slitaget på skivan kommer att vara i snitt hälften av slitaget på en vanlig skiva, eftersom du i det långa loppet kommer att spela det gröna spåret lika många gånger som det röda spåret. För den med en stor skivsamling i sin ägo är detta vanligtvis inte något stort problem, för en livstid räcker inte till särskilt många spelningar per skiva, men för en DJ som sliter på sina skivor, eller för den som råkar ha just denna låt som sin favorit, är detta något bra. (Många 7″-singlar som släppts under 80- och 90-talet har faktiskt varit av så låg kvalité att du endast kan lyssna på dem med full ljudkvalité 2-3 gånger.)

I teorin skulle man kunna ha olika information i de olika spåren, så att slumpen får avgöra vilken låt som spelas när man lägger på nålen, vilket Henrik Andersson (som donerade skivan till mig) faktiskt hävdar har gjorts.

(Jo, jag försökte komma nära med kameran och ta bilder på nålen, men för en sådan närbild har jag varken ljus eller stativ som håller måttet. Det kanske kommer längre fram.)

Flytta C64-program till din PC

Det finns en del olika strategier för att bevara (eller preservera) gamla Commodore 64-program för framtiden. Här presenterar jag en snabbguide för den som äger en SD2IEC och ett tomt SD-kort.

Hårdvarukrav:

Du behöver ha en C64 (eller annan 8-bitarsmaskin från Commodore, men jag antar att du har en C64), en diskdrive, en floppydisk du vill bevara (preservera), ett SD-kort att preservera floppydisken på och en SD2IEC-enhet.

Förberedelser av SD-kort (görs på din Windows-dator):

Se till att formatera kortet till FAT32. Placera ut en diskavbildning (D64-fil) innehållande DraCopy och en diskavbildning som är tom (vilket enkelt skapas med t.ex. DirMaster). Du bör nu ha ett FAT32-formaterat SD-kort med två diskavbildningar på (D64-filer).

Mata därefter in SD-kortet i din SD2IEC-enhet som är kopplad till din C64. Notera att det finns en hel drös olika varianter av SD2IEC. Vissa har en serieport för att man ska kunna koppla in en fysisk diskdrive i den, men om det saknas så går det bra att dra en sladd mellan din C64 och din diskdrive, och en sladd mellan din diskdrive och din SD2IEC-enhet.

Val av enhetsbeteckning:

Om inget annat är sagt, kommer både din SD2IEC och din diskdrive att ha enhetsbeteckning 8, vilket innebär att de inte kan användas samtidigt. Har du en SD2IEC med dipomkopplare eller en diskdrive med dipomkopplare kan du enkelt ändra så att den ena enheten har en annan enhetsbeteckning än 8. Jag valde att lägga min diskdrive (Commodore 1571) på enhet 10 och låta min SD2IEC ligga kvar på enhet 8. Se till att dina enheter har olika beteckning, och att du håller koll på vilken enhet som har vilken beteckning – i denna text kommer min enhet 8 vara SD2IEC och enhet 10 vara min diskdrive, men du måste tänka på hur du har konfigurerat hårdvaran.

Om du inte kan ändra enhetsbeteckning med dipomkopplare på någon av enheterna, får du använda ett kommando för att ändra enhetsbeteckning på din SD2IEC. Detta kommando ändrar enhetsbeteckningen från 8 till 9 (vilket jag inte gjort).

OPEN1,8,15,"U0>+CHR$(9)":CLOSE1

Men tänk på att jag har SD2IEC som 8 och min diskdrive som 10.

Därefter är det bara att leta reda på en floppydisk som du vill bevara, och stoppa in den i din diskdrive (som för mig är enhet 10). Ditt SD-kort med DraCopy och den tomma virtuella disken (som jag har valt att kalla för empty.d64) ska matas in i din SD2IEC.

Utför kopieringen:

Välj den virtuella disketten innehållande DraCopy som din diskett på enhet 8 (i mitt fall – tänk på att anpassa efter behov) genom följande kommando:

OPEN1,[enhet],15,"CD:[avbildning innehållande DraCopy]":CLOSE1

I mitt fall:

OPEN1,8,15,"CD:dc10d.d64":CLOSE1

Starta rätt version av DraCopy, beroende på vilken 8-bitarsmaskin du använder. Du kan se de tillgängliga versionerna genom att skriva LOAD"$",8 följt av LIST.

Jag hämtar rätt version för mig genom att skriva LOAD"DC64",8,1 och sedan RUN.

Innan du startar DraCopy, koppla loss diskavbildningen innehållande DraCopy. (Symbolen vänsterpil finns längst uppe till vänster på tangentbordet på din C64:a.)

OPEN1,8,15,"CD:[vänsterpil]":CLOSE1

Mounta sedan tomma virtuella disk.

OPEN1,8,15,"CD:EMPTY.D64":CLOSE1

DraCopy innehåller två fönster. Det ena har titeln S[enhet], t.ex. S08, och det andra har titeln D[enhet], t.ex. D09. Du väljer vilket fönster du ska arbeta i genom att trycka på vänsterpilen, längst uppe till vänster på tangentbordet.

I det ena fönstret, tryck på F2 tills du hittar källdisken (den fysiska) eller destinationsdisken (den virtuella). Tryck sedan F1 och för att visa diskens innehåll och bekräfta att du har valt rätt.

Därefter, tryck på vänsterpilen för att byta fönster och upprepa proceduren. F2 för att välja destinationsdisken eller källdisken och F1 för att visa innehåll.

När det ena fönstret innehåller källan och det andra fönstret innehåller destinationen, tryck på vänsterpilen till dess att källan har ett S framför sig och destinationen har ett D framför sig.

Därefter utför du kopieringen genom att trycka F8.

När kopieringen är klar och du har flyttat SD-kortet tillbaka till din PC, glöm inte att ge diskavbildningen ett vettigare filnamn än EMPTY.D64.

Begränsningar:

DraCopy kan endast kopiera disketter som inte innehåller några läsfel. Om du vill bevara ett program på en diskett som innehåller fel, bör du istället använda ett kopieringsprogram som kopierar individuella filer.

Resultat:

För min del valde hag en diskett innehållande bl.a. en låt som jag vet inte har digitaliserats för, nämligen Slow Down. Diskavbildningen finns att ladda hem här.

(Låten Slow Down är samplad från gruppen Loose Ends, 1986.)

Produktionskostnad/vinst-förhållandet för misslyckade uppföljare

Det är ofta ett säkert kort inom filmindustrin att göra en uppföljare, eftersom man kan spela an på en framgång. Det blir ett slags varumärkesexploatering som inte alltid utnyttjar sin fulla potential. Ibland blir inte uppföljaren lika bra som sin föregångare, och här är tre exempel på hur budget förhåller sig till bruttointäkt i filmserier som inte lyckades följa upp sitt original. De filmer som inte når upp till 1,0 har alltså inte dragit in pengarna de kostade att producera, och här är tre exempel på “dåliga uppföljare”. Så här ser en flopp ut:

Highlander, inspelningsbudget och bruttointäkt (miljoner dollar):

Förhållande, inspelningsbudget och bruttointäkt:

Stålmannen, är en filmserie som varit igång sedan 1940-talet, men för de tidigaste filmerna är det inte riktigt känt vilka pengar som var inblandade, så här är inspelningsbudget och bruttointäkt (miljoner dollar) en bit in i serien, när Christopher Reeve klev in i serien:

Förhållande, inspelningsbudget och bruttointäkt:

Här ser vi exempel på där pengarna som står på spel varit så extremt framgångsrik i sitt ursprungsförfarande, så att jag var tvungen att introducera en decimal. Och trots att uppföljaren var en total flopp, så har den så pass stöd från sitt varumärke att siffrorna framöver varit svarta. Precis som i fallet med Stjärnornas krig, så har vi inte riktigt facit för filmserien än, för fler filmer är planerade i serien.

Alla helgons blodiga natt, inspelningsbudget och bruttointäkt (miljoner dollar):

Förhållande, inspelningsbudget och bruttointäkt:

Del 1.

Produktionskostnad/vinst-förhållandet för Star Wars, Elm Street och The Terminator

Vissa filmer får uppföljare och spin offs till synes utan ände. Inte sällan efter en framgångsrik start, verkar bolagen vara redo att skjuta till mer pengar för att krama mer ur det varumärke som utgör en framgångsrik film. Här är pengarna som satsats och spelats in från tre kända filmserier.

Star Wars, inspelningsbudget och bruttointäkt (miljoner dollar):

Förhållande, inspelningsbudget och bruttointäkt:

Gissningsvis kommer den näst sista filmen dra in lite mer pengar över åren, och för den sista filmen finns ingen uppgift om intäkter än. Förmodligen kommer den vara mer lönsam i förhållande till vad den kostat än de två tidigare filmerna.

Terror på Elm Street, inspelningsbudget och bruttointäkt (miljoner dollar):

Förhållande, inspelningsbudget och bruttointäkt:

I den sista filmen, som är en nyinspelning av den första, är det inte Robert Englund som spelar Freddy Krueger.

The Terminator, inspelningsbudget och bruttointäkt (miljoner dollar):

Förhållande, inspelningsbudget och bruttointäkt:

Så troligen är det ett bra förhållande mellan satsade och tjänade pengar som avgör hur många uppföljare en film får.

Del 2.

Metabollar

Jag tänkte visa en komplett implementation av 2-dimensionella metabollar i C#. Denna teknik skulle även kunna användas i 3D. Effekten ser ut så här:

Effekten beskrivs här, och denna implementation prioriterar prestanda för att fungera i realtid. I videon ovan används 20 positiva bollar (alltså bollar som tenderar att smeta ihop) och 10 negativa bollar (alltså bollar som tenderar att sluka andra bollar) och samtliga rör sig i sinusbanor på en yta av 320×200 pixlar. Renderingen bygger på Windows Forms och klassen Bitmap.

För att få rörelserna mjuka har jag satt formulärets egenskap DoubleBuffered till true. Dessutom har jag placerat en Timer på formuläret och satt dess Interval till 1 för att få så bra prestanda som möjligt utan att köra dedikerat, men om man ställer ännu högre krav på användarupplevelsen, skulle man välja ett annat bibliotek än Windows Forms. Dessa inställningar har jag ställt in i Visual Studio, så de syns inte i koden.

Här är formulärets kod:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Metaballs
{
    public partial class Form1 : Form
    {
        private IRenderer _renderer;
        private Bitmap _bitmap;
        private int[,] _pixels;
        private double _counter = 0.0;
        private readonly List<Ball> _balls = new List<Ball>();
        
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            Refresh();
            _renderer = new MetaRenderer();
            _bitmap = new Bitmap(320, 200);
            _pixels = new int[320, 200];
            for (var i = 0; i < 30; i += 3)
            {
                _balls.Add(new Ball(MovementDescription.Randomize(), i, false));
                _balls.Add(new Ball(MovementDescription.Randomize(), i + 1, false));
                _balls.Add(new Ball(MovementDescription.Randomize(), i + 2, true));
            }
            timer1.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            //Clear array.
            for (var y = 0; y < 200; y++)
                for (var x = 0; x < 320; x++)
                    _pixels[x, y] = 0;

            foreach (var ball in _balls)
                _renderer.Apply(ball, _pixels);
            
            //Draw.
            _renderer.Draw(_pixels, _bitmap);
            foreach (var ball in _balls)
                ball.Tick(_counter);

            _counter += 1.0;
            Refresh();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            if (_bitmap == null)
                return;
            e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
            e.Graphics.DrawImage(_bitmap, 0, 0, 640, 400);
        }
    }
}

De resurser som används är _renderer som ansvarar för att bestämma hur bollarna påverkar varandra och hur det ska se ut, _bitmap som är ytan jag renderar till, _pixels som är min matris för beräkningar, _counter som används för sinusbanorna och _balls som är mina metabollar. Notera att jag, likt inställningarna jag nämnde tidigare, låtit Visual Studio mappa upp mina events, så dessa syns inte i koden. Men de kan läsas av från namngivningen av procedurerna.

Som synes av den sista kodraden zoomar jag resultatet med 50%. Om man vill se resultatet i naturlig storlek behöver man inte ange InterpolationMode (efter som ingen pixelinterpolering då sker) eller någon ny storlek (640, 400).

Jag har valt att peka ut renderingen med ett interface (IRenderer) eftersom det är mycket troligt att man vill ha olika renderingskod under tiden man designar sitt alster och när man är nöjd. T.ex. är det rimligt att man tydligt vill se sina bollar flytta sig över skärmen när man kalibrerar deras banor, men när det är färdigt vill man självklart se hur de påverkar varandra, eftersom det är det som är syftet (och det som visas i videon ovan). Här är IRenderer:

using System.Drawing;

namespace Metaballs
{
    public interface IRenderer
    {
        void Apply(Ball source, int[,] target);
        void Draw(int[,] source, Bitmap target);
    }
}

En debug-implementation kanske skulle låta Apply färgsätta de individuella bollarna olika och låta Draw visa det. Däremot är en produktionsimplementation av IRenderer hjärtat i programmet. Där skulle Apply addera eller subtrahera en boll på spelplanen, och Draw skulle visa en kontur baserat på det. En boll (Ball) representerar en position och en rörelse för en boll, och för att kunna hitta tröskelvärden när två bollar är nära varandra, består dessa av en cirkel med låga värden nära konturen och höga värden nära centrum. På så vis kan en godtycklig kontur skapas från summan som skapas när bollarna adderats till ytan (eller subtraherats, för de negativa bollarna). I min implementation är varje boll 98×98 pixlar stor, varför deras centrum ligger 49 pixlar i varje riktning från sin position, vilket framgår av Apply:

using System.Drawing;

namespace Metaballs
{
    public class MetaRenderer : IRenderer
    {
        private Color[] _colors;

        public MetaRenderer()
        {
            _colors = new Color[100];
            for (var i = 0; i < _colors.Length; i++)
                _colors[i] = Color.Black;
            var j = 60;
            for (var c = 0; c < 255; c += 30)
            {
                _colors[j] = Color.FromArgb(c, c, c);
                j++;
            }
            for (var c = 255; c >= 0; c -= 30)
            {
                _colors[j] = Color.FromArgb(c, c, c);
                j++;
            }
        }

        public void Apply(Ball source, int[,] target)
        {
            var ballX = (int)source.X - 49;
            var ballY = (int)source.Y - 49;
            for (var y = 0; y < 98; y++)
            {
                for (var x = 0; x < 98; x++)
                {
                    var xp = ballX + x;
                    if (xp < 0 || xp >= 320)
                        continue;
                    var yp = ballY + y;
                    if (yp < 0 || yp >= 200)
                        continue;
                    if (source.Negative)
                        target[xp, yp] -= DataDefinitions.Ball98[x, y];
                    else
                        target[xp, yp] += DataDefinitions.Ball98[x, y];
                }
            }
        }
        
        public void Draw(int[,] source, Bitmap target)
        {
            for (var y = 0; y < 200; y++)
            {
                for (var x = 0; x < 320; x++)
                {
                    target.SetPixel(x, y,
                        source[x, y] < _colors.Length
                        && source[x, y] >= 0
                            ? _colors[source[x, y]]
                            : Color.Black);
                }
            }
        }
    }
}

(Anti-aliasing-effekten uppnås av den toning som konfigureras i konstruktorn ovan.) Så här ser en boll ut:

using System;
   
   namespace Metaballs
   {
       public class Ball
       {
           private const double CenterX = 159.0;
           private const double CenterY = 99.0;
           private readonly MovementDescription _d;
           
           public double X { get; private set; }
           public double Y { get; private set; }
           public int Index { get; }
           public bool Negative { get; }
           
           public Ball(MovementDescription movementDescription, int index, bool negative)
           {
               _d = movementDescription;
               Index = index;
               Negative = negative;
               X = 159;
               Y = 99;
           }
   
           public void Tick(double counter)
           {
               X = CenterX + Math.Sin(counter / _d.SinFactor1) * _d.SinRadius1
                           + Math.Sin(counter / _d.SinFactor2) * _d.SinRadius2;
               Y = CenterY + Math.Cos(counter / _d.CosFactor1) * _d.CosRadius1
                           + Math.Cos(counter / _d.CosFactor2) * _d.CosRadius2;
           }
       }
   }

MovementDescription är en struktur som samlar data om hur sinusbanan ska se ut. Som man kunde ana från huvudprogrammet erbjuder strukturen en möjlighet att låta slumpen avgöra hastighet och radie.

using System;

namespace Metaballs
{
    public struct MovementDescription
    {
        private static Random Rnd;
        public double SinFactor1 { get; }
        public double SinFactor2 { get; }
        public double CosFactor1 { get; }
        public double CosFactor2 { get; }
        public double SinRadius1 { get; }
        public double SinRadius2 { get; }
        public double CosRadius1 { get; }
        public double CosRadius2 { get; }

        
        static MovementDescription()
        {
            Rnd = new Random();
        }
        
        public MovementDescription(
            double sinFactor1,
            double sinFactor2,
            double cosFactor1,
            double cosFactor2,
            double sinRadius1,
            double sinRadius2,
            double cosRadius1,
            double cosRadius2)
        {
            SinFactor1 = sinFactor1;
            SinFactor2 = sinFactor2;
            CosFactor1 = cosFactor1;
            CosFactor2 = cosFactor2;
            SinRadius1 = sinRadius1;
            SinRadius2 = sinRadius2;
            CosRadius1 = cosRadius1;
            CosRadius2 = cosRadius2;
        }

        public static MovementDescription Randomize() =>
            new MovementDescription(
                Rnd.Next(10, 100),
                Rnd.Next(10, 100),
                Rnd.Next(10, 100),
                Rnd.Next(10, 100),
                Rnd.Next(10, 100),
                Rnd.Next(10, 100),
                Rnd.Next(10, 100),
                Rnd.Next(10, 100)
            );
    }
}

Därmed är det enda som återstår själva bollen som ovan refereras till som Ball98, uppkallad efter sina sidors storlek. För att göra det enkelt, ritade jag boll genom att välja penselverktyget i Photoshop och skrev ett enkelt program som konverterade det hela till en int array i C#, enligt följande:

namespace Metaballs
{
    public class DataDefinitions
    {
        public static int[,] Ball98 = new int[98, 98]
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 3, 3, 2, 3, 2, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 4, 4, 4, 4, 5, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 3, 4, 3, 4, 5, 5, 5, 6, 5, 5, 6, 6, 6, 6, 5, 5, 6, 6, 6, 6, 5, 5, 4, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 3, 2, 2, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 5, 5, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 7, 6, 7, 8, 9, 9, 9, 9, 9, 10, 10, 9, 9, 10, 10, 10, 9, 9, 10, 9, 9, 8, 9, 8, 8, 8, 7, 7, 6, 6, 5, 4, 5, 4, 4, 3, 2, 3, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 8, 7, 7, 6, 6, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 5, 5, 6, 6, 6, 8, 8, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14, 14, 14, 15, 15, 14, 14, 15, 15, 14, 14, 13, 13, 13, 12, 11, 12, 11, 10, 9, 8, 8, 7, 6, 6, 5, 5, 4, 5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 7, 8, 8, 10, 10, 11, 11, 12, 12, 13, 14, 14, 15, 15, 16, 16, 16, 17, 17, 17, 17, 17, 17, 16, 16, 16, 15, 15, 14, 14, 14, 13, 11, 11, 10, 10, 9, 8, 8, 7, 7, 6, 5, 5, 4, 4, 3, 3, 3, 3, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 7, 8, 9, 9, 11, 11, 12, 13, 14, 15, 16, 16, 17, 18, 19, 19, 20, 20, 20, 20, 21, 20, 20, 20, 20, 19, 19, 18, 18, 17, 16, 16, 15, 14, 13, 12, 12, 11, 9, 9, 8, 7, 6, 6, 5, 4, 4, 3, 3, 3, 3, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11, 13, 13, 14, 15, 16, 16, 17, 19, 21, 21, 22, 22, 22, 22, 23, 23, 24, 24, 24, 23, 23, 22, 21, 21, 21, 19, 19, 19, 18, 17, 14, 15, 14, 12, 11, 10, 9, 8, 7, 7, 5, 5, 5, 4, 4, 3, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, 7, 7, 7, 9, 11, 11, 12, 13, 15, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 25, 26, 26, 27, 27, 28, 27, 27, 27, 27, 26, 25, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 14, 13, 12, 11, 11, 9, 7, 8, 6, 6, 6, 5, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 7, 7, 9, 9, 10, 11, 13, 14, 15, 17, 18, 19, 21, 23, 23, 25, 25, 26, 28, 28, 29, 31, 31, 31, 32, 32, 31, 31, 31, 31, 31, 30, 29, 28, 26, 25, 25, 23, 22, 21, 19, 18, 17, 15, 14, 13, 11, 11, 10, 8, 8, 7, 6, 6, 5, 4, 3, 3, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 20, 20, 21, 23, 26, 26, 28, 29, 30, 32, 33, 33, 35, 35, 36, 36, 36, 36, 36, 36, 35, 35, 34, 33, 32, 30, 29, 28, 27, 25, 23, 21, 21, 20, 18, 16, 15, 13, 12, 11, 9, 8, 8, 6, 6, 5, 4, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 20, 22, 24, 25, 27, 29, 30, 32, 34, 35, 37, 37, 39, 40, 41, 42, 42, 42, 42, 42, 42, 41, 40, 38, 37, 36, 35, 33, 32, 30, 29, 27, 25, 23, 22, 20, 18, 17, 15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 4, 4, 4, 3, 3, 2, 1, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 7, 8, 10, 10, 11, 12, 14, 16, 17, 19, 22, 24, 26, 27, 29, 31, 33, 35, 37, 39, 40, 42, 43, 44, 45, 46, 47, 48, 48, 48, 48, 47, 46, 46, 44, 44, 42, 40, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 16, 15, 13, 11, 10, 9, 8, 7, 5, 5, 5, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 7, 7, 8, 9, 11, 13, 14, 16, 18, 20, 22, 25, 27, 29, 30, 33, 35, 37, 40, 42, 43, 45, 47, 49, 50, 52, 53, 53, 54, 54, 54, 54, 53, 52, 52, 50, 49, 47, 45, 44, 41, 39, 38, 35, 33, 30, 29, 27, 25, 22, 20, 18, 16, 14, 13, 11, 9, 8, 7, 7, 5, 5, 5, 4, 3, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4, 4, 6, 7, 7, 9, 10, 11, 13, 15, 16, 18, 20, 23, 25, 27, 30, 32, 35, 38, 40, 43, 45, 47, 49, 51, 53, 55, 56, 58, 59, 60, 60, 60, 60, 60, 59, 59, 58, 56, 55, 53, 51, 50, 47, 45, 42, 40, 38, 35, 32, 30, 27, 24, 22, 20, 18, 16, 15, 13, 11, 10, 9, 7, 7, 6, 4, 4, 3, 3, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5, 6, 7, 9, 10, 11, 12, 15, 17, 19, 21, 23, 26, 29, 31, 34, 36, 39, 42, 45, 48, 50, 52, 56, 58, 60, 62, 63, 65, 66, 67, 68, 68, 68, 68, 67, 66, 66, 64, 62, 60, 58, 55, 53, 51, 48, 44, 42, 39, 36, 34, 30, 28, 26, 23, 21, 19, 16, 14, 12, 11, 10, 9, 7, 6, 5, 5, 4, 4, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 8, 10, 11, 12, 15, 16, 18, 21, 24, 26, 29, 32, 34, 37, 40, 43, 47, 49, 53, 56, 59, 62, 65, 67, 69, 71, 73, 74, 75, 76, 76, 76, 76, 75, 74, 73, 71, 69, 67, 65, 62, 59, 55, 53, 50, 47, 43, 40, 37, 34, 32, 29, 26, 24, 21, 18, 17, 14, 12, 11, 9, 7, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 17, 18, 20, 23, 26, 29, 32, 34, 38, 42, 45, 48, 52, 55, 59, 62, 65, 68, 72, 74, 77, 79, 81, 82, 83, 84, 84, 85, 84, 83, 82, 81, 79, 76, 74, 72, 69, 65, 61, 59, 56, 52, 49, 45, 41, 38, 35, 32, 28, 26, 23, 20, 19, 16, 14, 13, 10, 9, 8, 6, 6, 5, 5, 3, 3, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 8, 9, 10, 12, 14, 16, 18, 21, 23, 26, 29, 32, 35, 39, 42, 46, 49, 53, 57, 61, 65, 68, 72, 76, 79, 82, 85, 87, 89, 91, 92, 93, 93, 93, 93, 91, 91, 89, 86, 84, 82, 79, 76, 72, 69, 65, 61, 57, 54, 49, 45, 41, 38, 35, 32, 29, 26, 23, 21, 18, 15, 14, 11, 10, 9, 8, 6, 5, 5, 4, 3, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5, 6, 7, 9, 9, 11, 12, 15, 17, 20, 23, 25, 28, 32, 35, 39, 42, 46, 50, 54, 59, 63, 67, 71, 75, 80, 84, 87, 90, 93, 96, 98, 99, 100, 102, 102, 103, 102, 100, 99, 98, 96, 93, 90, 87, 83, 79, 75, 71, 67, 63, 59, 54, 51, 46, 42, 38, 35, 32, 28, 25, 23, 20, 17, 15, 12, 11, 10, 8, 7, 6, 5, 4, 4, 3, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 4, 4, 5, 6, 7, 8, 10, 11, 13, 14, 17, 19, 22, 25, 27, 30, 35, 39, 42, 46, 50, 56, 60, 65, 69, 73, 78, 83, 87, 92, 96, 99, 102, 106, 108, 110, 111, 113, 113, 113, 113, 111, 109, 107, 105, 102, 99, 95, 91, 87, 82, 78, 74, 70, 64, 60, 56, 50, 46, 42, 38, 34, 30, 27, 25, 22, 19, 17, 14, 13, 11, 10, 8, 7, 6, 5, 4, 4, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0}, 
{0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 9, 11, 11, 13, 16, 18, 21, 24, 27, 30, 34, 37, 42, 46, 50, 55, 61, 65, 70, 75, 80, 85, 90, 95, 100, 104, 107, 111, 115, 117, 120, 121, 122, 122, 122, 122, 121, 119, 117, 114, 111, 107, 104, 99, 94, 90, 85, 80, 75, 70, 65, 61, 55, 50, 46, 42, 37, 33, 30, 27, 24, 21, 18, 16, 13, 12, 10, 8, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0}, 
{0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 17, 20, 23, 26, 29, 33, 37, 40, 45, 50, 54, 60, 65, 70, 76, 83, 88, 92, 98, 103, 108, 112, 117, 121, 125, 127, 130, 131, 132, 133, 133, 132, 131, 130, 127, 125, 121, 117, 112, 108, 103, 97, 92, 87, 81, 77, 72, 66, 59, 54, 50, 45, 42, 37, 33, 30, 26, 23, 20, 17, 14, 12, 11, 9, 8, 7, 5, 5, 4, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0}, 
{0, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 12, 14, 16, 19, 22, 25, 28, 31, 34, 40, 45, 49, 54, 60, 65, 70, 76, 82, 88, 94, 100, 106, 112, 116, 121, 126, 130, 134, 137, 140, 141, 143, 144, 144, 143, 141, 140, 137, 134, 130, 125, 121, 117, 112, 106, 100, 95, 89, 82, 76, 71, 64, 59, 54, 49, 44, 40, 36, 32, 28, 25, 22, 19, 16, 14, 13, 11, 9, 8, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 0, 0}, 
{0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 5, 5, 6, 8, 10, 11, 12, 15, 17, 20, 23, 27, 30, 33, 38, 42, 47, 52, 58, 64, 70, 76, 83, 88, 94, 101, 108, 114, 120, 125, 131, 135, 139, 144, 148, 150, 152, 154, 154, 154, 154, 152, 150, 147, 143, 139, 135, 131, 125, 120, 114, 108, 101, 94, 88, 81, 75, 70, 64, 58, 52, 47, 43, 38, 34, 30, 27, 23, 20, 17, 15, 13, 11, 9, 7, 7, 5, 4, 4, 3, 2, 2, 2, 2, 1, 1, 0, 0}, 
{0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 9, 10, 11, 13, 16, 18, 22, 25, 28, 33, 37, 41, 46, 51, 56, 62, 68, 74, 81, 89, 94, 101, 108, 115, 121, 127, 134, 139, 144, 150, 154, 158, 160, 162, 164, 164, 164, 164, 162, 160, 157, 154, 150, 144, 139, 134, 129, 121, 115, 108, 101, 95, 89, 81, 74, 68, 62, 56, 51, 46, 41, 37, 33, 29, 25, 21, 18, 16, 13, 11, 10, 8, 7, 5, 4, 4, 4, 3, 2, 2, 1, 1, 1, 0, 0}, 
{0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 6, 6, 7, 9, 11, 13, 15, 18, 20, 24, 27, 30, 35, 38, 43, 49, 54, 60, 66, 72, 79, 86, 94, 101, 108, 115, 122, 129, 136, 143, 148, 153, 159, 163, 167, 171, 173, 175, 175, 175, 175, 173, 171, 167, 164, 160, 154, 148, 143, 136, 130, 122, 115, 108, 101, 94, 86, 79, 73, 66, 60, 55, 49, 44, 39, 34, 31, 27, 23, 20, 18, 15, 12, 11, 10, 8, 6, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0}, 
{0, 0, 1, 1, 2, 2, 2, 3, 4, 4, 6, 7, 8, 10, 12, 14, 16, 18, 21, 24, 27, 32, 37, 40, 46, 52, 58, 63, 70, 77, 84, 93, 99, 106, 114, 121, 129, 137, 144, 151, 158, 164, 169, 173, 177, 181, 183, 185, 186, 186, 186, 184, 181, 178, 174, 170, 164, 158, 151, 144, 137, 130, 121, 114, 106, 99, 92, 84, 77, 70, 63, 57, 52, 47, 41, 37, 32, 27, 24, 21, 18, 16, 13, 11, 10, 8, 6, 6, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0}, 
{0, 0, 1, 1, 2, 2, 2, 4, 4, 4, 6, 7, 8, 10, 13, 14, 16, 19, 22, 26, 29, 33, 38, 43, 48, 54, 61, 67, 73, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 159, 165, 173, 178, 183, 187, 191, 193, 195, 196, 196, 195, 193, 190, 187, 183, 178, 173, 167, 160, 152, 144, 136, 128, 120, 112, 104, 96, 88, 80, 74, 68, 61, 55, 49, 43, 38, 34, 30, 26, 22, 19, 17, 15, 12, 10, 8, 7, 6, 5, 4, 3, 2, 2, 2, 1, 1, 1, 1}, 
{0, 1, 1, 1, 2, 2, 2, 4, 4, 5, 7, 7, 9, 11, 13, 15, 17, 21, 24, 28, 31, 35, 40, 46, 51, 57, 64, 71, 77, 84, 92, 101, 110, 118, 127, 135, 143, 152, 160, 167, 174, 181, 187, 192, 196, 200, 203, 205, 206, 206, 205, 203, 200, 197, 192, 187, 181, 174, 167, 159, 152, 143, 135, 126, 118, 109, 101, 94, 85, 77, 71, 64, 57, 51, 47, 40, 35, 32, 28, 24, 20, 18, 16, 13, 11, 9, 8, 7, 5, 4, 3, 3, 2, 2, 1, 1, 1, 0}, 
{0, 1, 1, 1, 2, 2, 2, 4, 4, 5, 7, 7, 9, 11, 13, 16, 18, 21, 25, 29, 33, 37, 42, 47, 53, 59, 67, 74, 80, 89, 96, 105, 114, 122, 132, 140, 149, 159, 166, 174, 181, 189, 195, 200, 205, 208, 211, 214, 215, 214, 214, 212, 209, 205, 200, 195, 189, 181, 174, 166, 159, 149, 140, 131, 123, 114, 105, 98, 89, 80, 73, 66, 59, 53, 47, 42, 37, 32, 29, 24, 21, 18, 15, 13, 11, 9, 8, 7, 5, 4, 4, 3, 2, 2, 1, 1, 1, 0}, 
{0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 7, 8, 10, 12, 14, 16, 18, 22, 26, 28, 33, 38, 44, 49, 55, 62, 69, 76, 84, 93, 100, 109, 118, 127, 136, 146, 155, 164, 173, 181, 188, 195, 202, 208, 213, 217, 220, 222, 223, 223, 222, 220, 217, 213, 208, 202, 195, 188, 181, 173, 164, 155, 146, 136, 127, 118, 109, 100, 93, 84, 76, 69, 61, 56, 49, 43, 38, 33, 29, 25, 22, 18, 16, 14, 12, 10, 8, 7, 5, 5, 4, 3, 2, 2, 1, 1, 1, 1}, 
{1, 1, 1, 2, 2, 2, 3, 4, 5, 5, 7, 8, 10, 12, 15, 16, 20, 23, 27, 31, 35, 40, 45, 51, 57, 64, 72, 80, 88, 95, 103, 113, 122, 132, 141, 152, 161, 169, 179, 187, 195, 202, 209, 215, 220, 224, 227, 229, 230, 230, 229, 226, 224, 220, 215, 209, 202, 195, 187, 178, 170, 161, 151, 141, 131, 122, 113, 104, 96, 87, 79, 72, 64, 57, 51, 46, 40, 35, 31, 27, 23, 20, 16, 14, 12, 10, 9, 8, 6, 5, 4, 3, 2, 2, 2, 1, 1, 0}, 
{1, 1, 1, 2, 2, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 17, 21, 24, 28, 32, 36, 41, 46, 52, 59, 65, 73, 81, 89, 99, 107, 116, 127, 137, 147, 156, 165, 175, 183, 193, 201, 208, 214, 221, 227, 231, 234, 236, 237, 237, 236, 234, 231, 227, 221, 215, 208, 201, 193, 183, 175, 165, 155, 146, 136, 127, 117, 108, 99, 89, 81, 74, 66, 58, 52, 47, 41, 36, 31, 27, 24, 20, 18, 15, 12, 10, 9, 8, 6, 5, 4, 3, 2, 2, 1, 1, 1, 0}, 
{0, 1, 1, 1, 2, 3, 3, 4, 5, 6, 8, 9, 10, 12, 15, 18, 21, 24, 28, 32, 36, 42, 47, 54, 60, 67, 76, 84, 92, 101, 110, 120, 130, 140, 150, 159, 169, 178, 188, 198, 206, 213, 220, 226, 232, 236, 240, 242, 243, 243, 242, 240, 236, 232, 226, 220, 213, 206, 198, 188, 178, 168, 160, 149, 139, 130, 120, 111, 101, 91, 83, 76, 67, 60, 54, 47, 43, 38, 33, 29, 24, 21, 17, 15, 13, 11, 9, 8, 6, 5, 4, 3, 2, 2, 1, 1, 1, 1}, 
{0, 1, 1, 1, 2, 3, 3, 4, 5, 6, 8, 9, 11, 13, 15, 18, 21, 25, 29, 33, 37, 43, 48, 55, 62, 69, 77, 85, 93, 102, 112, 122, 131, 142, 153, 162, 172, 182, 193, 201, 209, 218, 225, 231, 236, 241, 244, 247, 247, 247, 247, 245, 241, 237, 231, 225, 218, 209, 200, 192, 183, 173, 162, 152, 142, 132, 122, 112, 102, 94, 85, 77, 69, 62, 54, 48, 43, 38, 33, 29, 25, 21, 19, 16, 13, 11, 9, 8, 6, 5, 4, 4, 3, 2, 2, 2, 1, 1}, 
{0, 1, 1, 1, 2, 3, 3, 4, 5, 6, 8, 9, 11, 13, 16, 19, 21, 25, 29, 33, 39, 44, 49, 55, 62, 70, 78, 86, 94, 103, 114, 124, 133, 143, 154, 164, 174, 185, 195, 204, 213, 221, 228, 234, 239, 244, 248, 250, 251, 251, 249, 248, 244, 240, 234, 228, 221, 213, 203, 195, 185, 175, 165, 154, 143, 133, 123, 113, 103, 95, 86, 78, 70, 63, 55, 49, 43, 38, 34, 29, 25, 21, 19, 16, 13, 11, 9, 8, 6, 5, 4, 4, 3, 2, 2, 2, 1, 1}, 
{1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 8, 10, 12, 14, 16, 19, 21, 25, 29, 34, 38, 44, 50, 56, 62, 70, 78, 87, 96, 105, 115, 125, 136, 145, 156, 167, 177, 187, 197, 206, 214, 223, 230, 236, 243, 247, 249, 252, 253, 253, 252, 250, 246, 242, 236, 230, 223, 215, 206, 197, 187, 177, 167, 156, 145, 135, 125, 115, 105, 96, 87, 79, 71, 63, 56, 49, 44, 38, 34, 30, 26, 22, 19, 16, 13, 11, 10, 8, 6, 5, 5, 3, 3, 2, 2, 1, 1, 1}, 
{1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 8, 10, 11, 13, 16, 18, 22, 26, 30, 34, 38, 44, 49, 56, 64, 71, 79, 87, 96, 105, 115, 125, 136, 146, 156, 166, 177, 187, 197, 207, 215, 224, 231, 237, 244, 248, 251, 253, 254, 255, 253, 251, 247, 243, 238, 230, 223, 216, 207, 198, 188, 177, 167, 156, 146, 136, 125, 115, 106, 96, 87, 79, 71, 63, 56, 50, 44, 38, 33, 29, 26, 22, 18, 16, 13, 11, 10, 8, 6, 5, 4, 3, 3, 2, 2, 1, 1, 1}, 
{1, 1, 1, 2, 2, 3, 3, 5, 5, 6, 8, 10, 11, 13, 16, 18, 22, 26, 30, 34, 38, 44, 50, 56, 63, 71, 79, 87, 96, 106, 115, 125, 136, 146, 157, 167, 177, 187, 197, 207, 216, 224, 231, 237, 243, 247, 251, 253, 254, 254, 253, 250, 247, 243, 237, 231, 224, 216, 207, 198, 188, 178, 167, 156, 146, 136, 125, 115, 105, 96, 87, 79, 71, 64, 56, 49, 44, 38, 34, 29, 25, 22, 18, 16, 13, 11, 10, 8, 6, 5, 4, 3, 3, 3, 2, 1, 1, 1}, 
{1, 1, 1, 2, 2, 3, 3, 4, 5, 8, 8, 9, 11, 13, 16, 19, 22, 26, 29, 33, 38, 44, 50, 55, 62, 71, 79, 87, 96, 105, 114, 124, 135, 145, 156, 166, 177, 187, 197, 206, 215, 223, 230, 236, 242, 246, 250, 252, 253, 253, 253, 251, 247, 242, 236, 230, 223, 215, 206, 197, 187, 177, 167, 156, 145, 135, 125, 115, 105, 96, 87, 79, 71, 63, 56, 49, 44, 39, 34, 29, 26, 22, 18, 16, 14, 12, 9, 8, 6, 5, 5, 4, 3, 3, 2, 1, 1, 1}, 
{1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 8, 9, 11, 13, 16, 19, 21, 25, 29, 34, 38, 44, 49, 55, 62, 70, 78, 86, 95, 104, 113, 123, 133, 143, 154, 165, 175, 185, 194, 203, 213, 221, 228, 234, 239, 244, 246, 249, 251, 251, 250, 248, 244, 239, 234, 228, 221, 213, 203, 195, 185, 175, 165, 154, 143, 133, 123, 113, 103, 95, 86, 78, 70, 62, 55, 49, 43, 38, 33, 28, 25, 22, 18, 16, 13, 11, 9, 8, 6, 5, 4, 3, 3, 2, 2, 2, 1, 0}, 
{1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 8, 9, 11, 13, 16, 19, 21, 25, 29, 33, 37, 43, 49, 55, 61, 69, 77, 85, 94, 103, 112, 122, 132, 142, 152, 163, 173, 182, 192, 201, 209, 217, 224, 230, 237, 241, 244, 246, 247, 248, 246, 242, 241, 237, 231, 225, 217, 209, 201, 192, 182, 173, 162, 152, 142, 132, 122, 112, 102, 94, 85, 76, 68, 62, 54, 49, 43, 38, 33, 28, 25, 22, 18, 16, 13, 11, 9, 8, 6, 5, 5, 4, 3, 2, 2, 1, 1, 0}, 
{1, 1, 2, 2, 2, 3, 3, 4, 5, 6, 8, 9, 10, 12, 15, 18, 21, 25, 28, 32, 36, 42, 48, 53, 61, 68, 76, 84, 91, 100, 109, 119, 130, 140, 149, 159, 169, 179, 189, 198, 206, 214, 221, 227, 232, 236, 240, 242, 243, 243, 242, 240, 236, 232, 226, 220, 213, 206, 198, 188, 179, 170, 160, 149, 139, 130, 119, 109, 101, 92, 83, 74, 69, 61, 53, 47, 43, 38, 32, 28, 25, 20, 18, 15, 13, 11, 9, 8, 6, 5, 5, 3, 2, 2, 2, 1, 1, 0}, 
{1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 8, 9, 10, 12, 15, 18, 20, 24, 27, 31, 36, 41, 46, 52, 59, 65, 73, 81, 90, 98, 108, 117, 126, 136, 146, 155, 165, 174, 184, 193, 201, 209, 216, 221, 226, 231, 234, 236, 237, 237, 236, 234, 231, 226, 221, 215, 208, 201, 193, 184, 175, 166, 157, 146, 136, 127, 116, 107, 98, 90, 82, 73, 65, 59, 52, 46, 41, 35, 31, 27, 23, 20, 18, 15, 13, 11, 9, 8, 6, 5, 4, 3, 2, 2, 1, 1, 1, 1}, 
{1, 1, 2, 2, 2, 2, 3, 5, 5, 6, 7, 8, 10, 12, 15, 17, 19, 23, 27, 31, 35, 40, 45, 51, 58, 63, 71, 80, 88, 96, 105, 113, 122, 131, 142, 152, 160, 169, 179, 187, 195, 202, 209, 215, 220, 224, 227, 229, 230, 230, 229, 226, 224, 220, 215, 209, 202, 195, 187, 178, 170, 161, 151, 142, 131, 122, 113, 103, 95, 88, 80, 72, 64, 57, 52, 46, 40, 34, 30, 27, 23, 19, 17, 15, 13, 11, 9, 7, 6, 5, 4, 3, 2, 2, 1, 1, 1, 0}, 
{0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 7, 8, 10, 12, 15, 17, 19, 23, 26, 29, 33, 38, 44, 49, 55, 61, 69, 77, 85, 93, 100, 109, 118, 127, 137, 147, 155, 164, 172, 180, 188, 195, 202, 208, 213, 217, 220, 222, 223, 223, 222, 220, 217, 213, 208, 202, 195, 188, 181, 173, 163, 155, 146, 136, 127, 118, 109, 101, 93, 84, 75, 69, 62, 55, 49, 44, 38, 34, 29, 25, 22, 18, 16, 14, 12, 9, 8, 7, 5, 4, 4, 3, 3, 2, 1, 1, 1, 1}, 
{1, 1, 1, 1, 2, 2, 3, 4, 4, 5, 7, 8, 9, 11, 13, 15, 18, 21, 25, 28, 32, 37, 41, 47, 53, 59, 66, 74, 81, 88, 96, 105, 114, 123, 132, 141, 149, 158, 167, 174, 181, 188, 195, 200, 205, 209, 212, 214, 214, 215, 214, 211, 208, 205, 200, 194, 189, 182, 174, 166, 157, 149, 142, 131, 122, 114, 105, 97, 89, 81, 72, 66, 59, 53, 47, 42, 37, 32, 28, 24, 21, 18, 15, 13, 11, 9, 8, 7, 5, 4, 4, 3, 3, 2, 1, 1, 1, 0}, 
{1, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 8, 9, 11, 13, 15, 17, 20, 23, 27, 31, 35, 40, 46, 51, 57, 64, 70, 78, 85, 92, 100, 109, 118, 126, 135, 143, 151, 160, 167, 173, 180, 187, 192, 196, 199, 202, 205, 205, 206, 205, 203, 200, 196, 192, 187, 180, 174, 167, 159, 151, 143, 135, 126, 118, 110, 100, 92, 85, 78, 70, 64, 57, 52, 46, 40, 35, 31, 28, 24, 21, 17, 15, 13, 11, 9, 8, 6, 5, 4, 4, 2, 2, 2, 1, 1, 1, 0}, 
{0, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 8, 8, 10, 13, 14, 16, 19, 23, 26, 28, 34, 39, 43, 48, 54, 60, 66, 74, 81, 88, 96, 104, 112, 120, 129, 136, 144, 152, 160, 166, 172, 178, 183, 187, 190, 193, 195, 196, 196, 195, 193, 191, 187, 183, 178, 172, 166, 160, 152, 144, 136, 128, 121, 113, 104, 96, 88, 81, 74, 66, 61, 55, 49, 43, 38, 33, 29, 26, 22, 19, 16, 15, 13, 10, 8, 7, 6, 4, 4, 4, 2, 2, 2, 2, 1, 1, 0}, 
{0, 1, 1, 1, 1, 2, 2, 3, 4, 4, 6, 7, 8, 10, 12, 14, 16, 18, 21, 24, 28, 31, 36, 41, 46, 51, 57, 64, 70, 77, 83, 92, 99, 106, 114, 122, 129, 137, 144, 151, 158, 163, 169, 174, 178, 181, 183, 185, 186, 186, 185, 183, 181, 178, 174, 169, 163, 157, 151, 144, 137, 129, 121, 114, 106, 99, 93, 84, 77, 70, 64, 58, 52, 46, 40, 37, 32, 27, 24, 21, 18, 15, 13, 12, 10, 8, 7, 6, 4, 4, 3, 2, 2, 2, 1, 1, 1, 0}, 
{0, 1, 1, 1, 1, 2, 3, 3, 4, 4, 5, 6, 8, 10, 11, 12, 15, 18, 20, 24, 27, 30, 35, 39, 44, 48, 54, 60, 66, 73, 79, 86, 94, 101, 108, 115, 122, 129, 136, 143, 148, 154, 159, 164, 168, 171, 173, 175, 175, 175, 175, 173, 171, 168, 164, 159, 155, 149, 143, 136, 129, 122, 116, 108, 101, 94, 86, 79, 73, 66, 60, 54, 49, 43, 39, 34, 29, 26, 23, 20, 18, 15, 13, 11, 10, 8, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 0, 0}, 
{0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 5, 6, 7, 9, 10, 11, 13, 16, 18, 22, 25, 28, 33, 36, 41, 46, 51, 56, 62, 68, 75, 80, 87, 94, 101, 108, 115, 121, 127, 134, 139, 145, 150, 154, 157, 160, 162, 164, 165, 165, 164, 163, 160, 157, 154, 150, 146, 140, 134, 127, 121, 115, 108, 102, 94, 88, 81, 74, 68, 62, 56, 51, 46, 41, 37, 32, 27, 24, 21, 18, 16, 14, 12, 10, 8, 6, 6, 5, 4, 3, 3, 2, 2, 2, 1, 1, 0, 0}, 
{0, 1, 1, 1, 1, 1, 2, 3, 3, 3, 5, 6, 7, 7, 9, 11, 13, 15, 17, 20, 23, 26, 31, 34, 38, 42, 47, 52, 58, 63, 69, 76, 82, 88, 94, 101, 108, 113, 120, 126, 131, 135, 141, 144, 147, 150, 152, 154, 155, 155, 154, 153, 150, 147, 144, 140, 136, 130, 125, 120, 114, 108, 101, 95, 88, 81, 75, 70, 64, 58, 53, 48, 43, 38, 33, 30, 27, 23, 20, 17, 15, 13, 11, 9, 8, 6, 6, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0}, 
{0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 14, 17, 19, 21, 25, 29, 32, 36, 39, 43, 48, 54, 59, 64, 71, 77, 82, 88, 94, 100, 105, 111, 116, 121, 125, 131, 134, 137, 140, 141, 143, 144, 144, 143, 142, 140, 137, 134, 130, 126, 122, 116, 111, 105, 100, 94, 88, 82, 76, 71, 65, 60, 54, 49, 44, 41, 36, 31, 28, 25, 22, 19, 16, 14, 12, 10, 9, 8, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 0, 0}, 
{0, 1, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 17, 20, 23, 26, 29, 33, 37, 41, 44, 50, 55, 59, 65, 72, 76, 81, 87, 93, 98, 103, 108, 112, 116, 121, 125, 127, 130, 131, 133, 133, 133, 132, 131, 130, 127, 124, 121, 117, 113, 108, 103, 98, 93, 88, 82, 76, 71, 65, 59, 55, 50, 45, 41, 36, 33, 29, 26, 23, 20, 17, 15, 13, 11, 10, 8, 7, 5, 5, 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 5, 6, 7, 8, 10, 12, 13, 16, 19, 21, 24, 27, 31, 34, 38, 41, 46, 50, 55, 61, 65, 70, 75, 81, 85, 90, 95, 99, 103, 107, 111, 115, 117, 119, 121, 122, 122, 122, 122, 120, 120, 116, 113, 111, 108, 104, 100, 95, 90, 86, 81, 75, 70, 65, 60, 55, 50, 46, 41, 37, 33, 30, 27, 24, 21, 19, 16, 13, 12, 10, 8, 7, 7, 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0}, 
{0, 0, 1, 1, 1, 1, 1, 2, 3, 3, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 17, 19, 22, 25, 28, 31, 34, 38, 42, 46, 51, 56, 60, 65, 70, 74, 78, 82, 87, 91, 95, 99, 102, 106, 108, 109, 111, 112, 113, 112, 112, 111, 110, 107, 104, 102, 99, 95, 91, 87, 82, 78, 73, 70, 65, 60, 55, 50, 46, 42, 38, 34, 31, 28, 25, 22, 19, 17, 15, 13, 11, 9, 7, 6, 6, 5, 4, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4, 4, 6, 6, 7, 9, 10, 11, 13, 15, 17, 20, 23, 26, 29, 32, 35, 38, 42, 46, 50, 54, 59, 63, 67, 72, 76, 80, 83, 87, 90, 94, 95, 97, 99, 100, 102, 102, 102, 102, 101, 99, 98, 96, 93, 90, 87, 83, 80, 76, 72, 67, 63, 58, 55, 51, 46, 42, 39, 36, 32, 28, 25, 23, 20, 17, 15, 13, 11, 10, 8, 6, 6, 6, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0}, 
{0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 3, 4, 5, 6, 6, 8, 9, 10, 12, 14, 16, 18, 21, 23, 26, 29, 32, 35, 38, 42, 45, 49, 53, 57, 60, 65, 69, 72, 76, 79, 81, 85, 87, 89, 91, 92, 93, 93, 93, 93, 92, 91, 89, 87, 85, 81, 79, 76, 72, 69, 65, 61, 57, 53, 49, 45, 42, 39, 35, 31, 28, 26, 23, 21, 18, 15, 14, 12, 10, 9, 8, 6, 6, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 3, 3, 3, 5, 6, 6, 7, 8, 10, 12, 13, 14, 17, 18, 20, 23, 26, 28, 31, 35, 38, 41, 45, 48, 52, 55, 59, 62, 65, 68, 72, 74, 76, 79, 81, 82, 83, 84, 84, 85, 84, 83, 83, 81, 79, 77, 73, 71, 69, 65, 62, 59, 55, 52, 48, 45, 41, 38, 35, 32, 28, 25, 23, 21, 19, 16, 14, 13, 11, 9, 8, 7, 5, 5, 5, 4, 3, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 4, 5, 5, 7, 7, 8, 10, 11, 12, 15, 16, 18, 21, 24, 26, 29, 32, 34, 37, 40, 43, 47, 50, 53, 56, 59, 62, 65, 67, 69, 71, 73, 74, 75, 76, 76, 77, 76, 75, 74, 73, 71, 69, 66, 64, 62, 59, 56, 53, 49, 47, 43, 40, 37, 34, 32, 29, 26, 24, 21, 19, 17, 14, 12, 11, 10, 8, 7, 5, 5, 4, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 9, 10, 11, 13, 15, 16, 19, 21, 23, 26, 29, 30, 33, 37, 39, 42, 44, 47, 51, 53, 55, 58, 60, 62, 63, 65, 66, 67, 68, 69, 69, 68, 67, 66, 65, 63, 61, 60, 58, 55, 53, 51, 47, 44, 42, 39, 36, 34, 30, 28, 26, 23, 21, 19, 16, 14, 12, 11, 10, 9, 7, 7, 6, 4, 4, 3, 3, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5, 6, 8, 9, 10, 11, 14, 15, 16, 18, 20, 22, 24, 27, 30, 32, 34, 38, 40, 43, 46, 48, 49, 51, 53, 55, 56, 58, 59, 59, 61, 61, 61, 60, 59, 59, 58, 56, 55, 53, 51, 50, 47, 45, 43, 40, 38, 34, 32, 30, 27, 25, 23, 20, 19, 17, 15, 13, 11, 10, 9, 8, 7, 6, 5, 4, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 5, 6, 7, 8, 8, 9, 12, 13, 14, 16, 18, 20, 22, 24, 27, 29, 31, 33, 35, 37, 40, 42, 43, 45, 47, 49, 50, 51, 53, 54, 54, 53, 53, 54, 53, 52, 51, 50, 49, 47, 45, 44, 41, 39, 37, 35, 33, 31, 28, 26, 25, 22, 20, 18, 17, 15, 13, 11, 9, 8, 7, 7, 5, 5, 4, 4, 3, 3, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 3, 3, 3, 4, 4, 5, 6, 7, 7, 8, 10, 11, 12, 15, 16, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 38, 40, 42, 44, 44, 46, 46, 46, 47, 47, 47, 48, 47, 46, 46, 44, 43, 42, 40, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 16, 14, 14, 11, 10, 9, 7, 6, 6, 5, 4, 4, 3, 3, 3, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 6, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 20, 22, 24, 25, 27, 29, 31, 33, 34, 35, 37, 38, 39, 41, 41, 41, 42, 42, 42, 42, 42, 40, 39, 40, 38, 37, 35, 34, 33, 31, 29, 27, 25, 23, 22, 20, 19, 17, 15, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 3, 3, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 5, 5, 5, 7, 7, 8, 9, 11, 12, 13, 15, 16, 18, 20, 20, 21, 24, 25, 26, 28, 29, 30, 32, 33, 34, 35, 35, 36, 36, 36, 36, 36, 36, 36, 35, 33, 32, 31, 31, 30, 28, 26, 25, 24, 22, 21, 20, 18, 17, 15, 13, 12, 11, 9, 8, 7, 7, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 5, 5, 5, 5, 6, 7, 9, 10, 10, 11, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 25, 26, 28, 28, 29, 30, 31, 31, 32, 31, 31, 31, 31, 31, 30, 29, 28, 28, 27, 26, 25, 23, 22, 21, 19, 18, 17, 15, 14, 13, 11, 10, 9, 9, 7, 7, 5, 5, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 8, 10, 11, 12, 13, 15, 16, 16, 18, 19, 20, 22, 22, 23, 23, 25, 26, 26, 27, 27, 27, 27, 27, 27, 27, 26, 26, 25, 25, 24, 23, 22, 21, 20, 19, 18, 16, 15, 14, 13, 12, 11, 10, 9, 9, 8, 7, 6, 5, 4, 4, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 2, 1, 1, 2, 3, 3, 3, 3, 5, 5, 5, 6, 7, 8, 8, 9, 10, 11, 12, 14, 14, 16, 17, 18, 17, 20, 21, 21, 21, 22, 23, 23, 23, 23, 23, 24, 24, 23, 23, 22, 23, 21, 21, 20, 19, 19, 18, 17, 15, 14, 13, 12, 11, 10, 9, 8, 7, 7, 6, 5, 5, 4, 4, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 11, 11, 12, 14, 14, 15, 16, 17, 17, 18, 19, 19, 20, 20, 20, 20, 20, 21, 21, 20, 20, 19, 19, 19, 18, 17, 16, 16, 15, 14, 14, 12, 12, 11, 9, 9, 8, 7, 6, 6, 5, 5, 4, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 6, 7, 8, 9, 9, 9, 10, 12, 12, 12, 13, 14, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 16, 16, 16, 15, 14, 14, 14, 12, 12, 11, 10, 10, 9, 8, 7, 7, 6, 5, 5, 5, 4, 4, 3, 3, 3, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 10, 10, 10, 10, 11, 13, 13, 12, 14, 14, 14, 15, 15, 14, 14, 15, 15, 14, 13, 14, 13, 12, 12, 12, 12, 10, 10, 9, 8, 8, 7, 7, 6, 6, 5, 4, 4, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 11, 12, 12, 12, 13, 13, 12, 12, 13, 12, 12, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 4, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 4, 3, 4, 6, 6, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 10, 10, 9, 10, 11, 10, 10, 11, 10, 10, 10, 10, 9, 9, 9, 9, 8, 7, 7, 7, 6, 5, 4, 5, 5, 4, 3, 3, 4, 3, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 8, 8, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 3, 3, 4, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 4, 3, 4, 4, 5, 5, 5, 6, 6, 5, 5, 5, 6, 6, 5, 5, 6, 6, 5, 6, 5, 5, 4, 4, 5, 4, 3, 3, 4, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 3, 4, 4, 4, 4, 5, 5, 5, 4, 4, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 2, 2, 2, 3, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 3, 3, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
    }
}

Mycket nöje!

Harter-Heighways drakkurva

Harter-Heighways drakkurva är en enkel och vacker linjär fraktal med många intressanta attribut. Dels är dess kontur självrepetitiv och kan pusslas ihop med lika dana konturer på många olika sätt, och linjen som kurvan består av korsar aldrig sig själv, oavsett hur lång drakkurva man väljer att rita. En drakkurva kan beskrivas som en serie av höger- och vänstersvängar. Första iterationen består av en sväng, normalt höger. Alla iterationer efter den första innehåller lika många instruktioner som den föregående, multiplicerat med två och plus 1. Alltså 1, 3, 7, 15, 31, och så vidare. Den första generationen består alltså av en högersväng (H):

H

För att skapa nästa iteration utgår man från den föregående och lägger till svängarna från den, baklänges, samtidigt som man byter ut högersvängar till vänstersvängar (V). Man avgränsar föregående iteration från den nya iterationen med en högersväng, alltså:

HHV

Tecken 1 (H) är första iterationen, nästa tecken 2 (H) är avgränsaren mellan den gamla och nya generationen, och den sista vänstersvängen (V) är vårt H från första iterationen, ersatt med ett V. Vidare, HHV baklänges blir VHH och byter man ut alla H mot V och alla V mot H så får man HVV. Då är generation tre alltså HHV, H som avgränsare och sist HVV.

HHVHHVV

Och så vidare! Nästa generation kompletteras med föregående generation baklänges, med höger ändrat till vänster och vänster ändrat till höger. Och ett H i mitten:

HHVHHVVHHHVVHVV

Ett papper kan vikas som en drakkurva, men inte i särskilt många generationer. I C# skulle man kunna använda en sådan här struktur för att beskriva ett enda steg i drakkurvan.

public struct DragonStep
{
    public DragonStep(bool isRightTurn)
            : this(isRightTurn, false) { }
    public DragonStep(bool isRightTurn,
        bool isIterationSeparator)
    {
        IsRightTurn = isRightTurn;
        IsIterationSeparator = isIterationSeparator;
    }
    public bool IsRightTurn { get; set; }
    public bool IsIterationSeparator { get; set; }
}

Egentligen behövs bara en boolesk egenskap för att hålla reda på om steget representerar en höger- eller vänstersväng, men jag vill hålla reda på var en generation börjar och slutar för att kunna rendera den med olika färger.

Här är listklassen som anropas för att bygga kurvan. Funktionen GetDragonSequence skapar en sekvens av höger och vänster-svängar i form av en lista av strukturen vi nyss såg. När man fått listan, handlar det bara om att välja en startposition och en startriktning, och sedan svänga åt höger eller vänster enligt vad som står i listan.

public class DragonCurveGenerator
{
    public static List<DragonStep> GetDragonSequence(int generations)
    {
        var ret = new List<DragonStep>();
        if (generations <= 0)
            return ret;
        if (generations > 0)
            ret.Add(new DragonStep(true));
        if (generations <= 1)
            return ret;
        for (var i = 1; i < generations; i++)
        {
            var lastGenerationFinalIndex = ret.Count - 1;
            ret.Add(new DragonStep(truetrue));
            for (var x = lastGenerationFinalIndex; x >= 0 ; x--)
                ret.Add(new DragonStep(!ret[x].IsRightTurn));
        }
        return ret;
    }
}

Jag definierar upp de möjliga riktningarna med en enumeration, för enkelhetens skull:

public enum Direction { Up, Right, Down, Left }

Sen är det bara en fråga om att rita kurvan, alltså följa sekvensen av svängar. Här får varje sväng representeras av en pixel. Notera att jag utnyttjar avgränsaren mellan iterationer för färgsättning.

public class DragonCurveRenderer
{
    public static void Draw(List<DragonStep> steps,
        Graphics g, Point startPosition, Direction startDirection,
        Brush color1, Brush color2, int first)
    {
        if (steps == null || steps.Count <= 0)
            return;
        var direction = startDirection;
        var x = startPosition.X;
        var y = startPosition.Y;
        var color = color1;
        var index = 0;
        foreach (var step in steps)
        {
            if (step.IsIterationSeparator)
            {
                color = color == color1 ? color2 : color1;
                index++;
            }
            if (index >= first)
                g.FillRectangle(color, x, y, 1, 1);
            ApplyDirection(direction, ref x, ref y);
            if (step.IsRightTurn)
            {
                if ((int) direction < 3)
                    direction++;
                else
                    direction = Direction.Up;
            }
            else
            {
                if (direction > 0)
                    direction--;
                else
                    direction = Direction.Left;
            }
        }
    }
    private static void ApplyDirection(Direction direction,
        ref int x, ref int y)
    {
        switch (direction)
        {
            case Direction.Up:
                y--;
                break;
            case Direction.Right:
                x++;
                break;
            case Direction.Down:
                y++;
                break;
            case Direction.Left:
                x--;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}

Bilden föreställer en drakkurva på 18 generationer där generation 6 (med index 5) är den förste som ritas ut. Alltså totalt 13 fält, varannan är vit, varannan är svart. Så här kan användningen se ut (Windows Forms):

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace DragonCurve
{
    public partial class Form1 : Form
    {
        private List<DragonStep> DragonSteps { get; set; }
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            DragonSteps = DragonCurveGenerator.GetDragonSequence(18);
        }
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            var startPoint = new Point(550, 450);
            DragonCurveRenderer.Draw(DragonSteps, e.Graphics,
                startPoint, Direction.Up, Brushes.White, Brushes.Black, 5);
        }
    }
}

Två nya SID-låtar

Jag har slängt ihop två nya SID-låtar som kan avnjutas på din Commodore 64.

Den ena (the_roger_boogie.sid) är ett resultat från ett demo-jam på jobbet tillsammans med Klas Dahlén, Roger Johansson och Erik Sandberg. Den är egenkomponerad och använder en samtida playerrutin (SID 8580). Förhandsgranskning finns på YouTube.

Den andra, som är en cover på låten I Touch Myself av Divinyls, slände jag ihop igår kväll. Den körs på en rutin som är anpassad till den gamla brödburken (SID 6582).

Ladda gärna hem filerna här!

Föreläsning om Commodore 64

Tisdagen den 27 september kl. 18:00 bjuder ABF i Örebro på en föreläsning om Commodore 64, i ABF:s lokaler på Fredsgatan 18 i Örebro.

Commodore 64 är fortfarande världens mest sålda hemdator, och trots att det gått 34 år sedan den introducerades på marknaden, släpps fortfarande nya spel till systemet, som fortfarande imponerar när det gäller grafik och ljud. Commodore 64 slog inte bara konkurrenterna Sinclair, Texas Instruments och Atari på fingrarna under 80-talet, utan lyckades överleva dem alla. Vad var det som gjorde att just Commodore 64 engagerade så många användare, programmerare, grafiker och musiker? Vad kunde datorn som inga andra system klarade av? Och vad är det som fortfarande lockar med just Commodore 64?

Föreläsare är Anders Hesselbom som har varit hemdatoranvändare sedan 1982 och idag är en inbiten programmerare och Commodore-entusiast.
I denna föreläsning berättar han varför just Commodore 64 sticker ut i jämförelse med andra system.

Hörselslinga finns.
Fika finns till försäljning

Facebook-event finns här. Föreläsningen är gratis och öppen för allmänheten. Välkommen!

Captured (Lars Hård)

An attempt to cover the C64 classic Captured soundtrack by Lars Hård.

Om du vill höra mer musik, mitt album från 2004 (tillsammans med Tommy Deile) finns på nätet här: Single Point of Failure – This time we are both

Mina C64-covers finns här: http://www.remix64.com/act/anders-hesselbom/

Dessutom, om du vill lyssna på mitt sommarprat i podcasten Kvack, om bl.a. min musik, så finns den här: http://kvackyou.se/2016/07/kvacksnack-sommar-2016-anders-hesselbom/

Gosbust 0.9.2

Version 0.9.2 av Gosbust finns nu att ladda hem. Tillåt mig presentera några exempel på vad aktuell version kan göra.

Det stora tillskottet handlar om stödet för arrayer. Kommandot ARRY tar ett namn (A-Z, A#-Z#, A$-Z$ eller A?-Z?) samt önskat antal element. 5 ger fem element med index 0-4.

ARRY A 5

Detta kolliderar inte med en eventuell enskild buffer som heter A. R$ och R$[3] lagras i olika minnesstrukturer. För att nå individuella element används hakparenteser. Detta skapar ett slumptal i arrayen A’s första element:

RAND A[0]

Om jag läser av element 0 borde jag se ett heltal, vilket som helst men troligen inte 0.

<<A[0]

Element 1 borde däremot fortfarande ha värdet 0, eftersom ingen tilldelning gjorts än.

<<A[1]

Om jag skulle önska ha ett slumptal i samtliga element i arrayen A, kan jag ersätta indexet med tre punkter:

RAND A[...]

Nu kan jag läsa av vilket element som helst (0-4) och få ett värde. Om jag skulle önska se samtliga element så kan jag skriva följande:

<<A[...]

Modulus kan inte appliceras på samtliga element i en array, så skulle jag önska att alla slumptal ska ligga mellan 0 och 9 kan jag skriva följande:

0>>I Mod: A[I]%10>>A[I] I++ I<10>>R? GOTO R? Mod

Om du använvänder konsollen så skulle bubbelsortering av 10 tal mellan 0 och 99 skulle se ut så här:

10 ARRY A 10 RAND A[...]
20 0>>I Mod: A[I]%100>>A[I] I++ I<10>>R? GOTO R? Mod
30 <<"Ej sorterat:" <<A[...]
40 0>>I 0>>J
50 Repeat: A[I]>A[J]>>S? GOSB S? Swap
60 I++ I>9>>I? GOSB I? NextOuter GOTO 1 Repeat
70 ShowResult: <<"Sorterat:" <<A[...]
80 QUIT 0
90 (Swap A[I]<>A[J] )
100 (NextOuter 0>>I J++ J>9>>J? GOTO J? ShowResult )

Notera att QUIT 0 lika gärna kan ersättas med RTRN. Om callstacken är tom har dessa två kommandon samma betydelse. Men om man skulle vilja avsluta med en felkod så kan QUIT n användas.

(Om du läser in en fil istället för att använda konsollen behövs inte radnumren.)

Mycket nöje!

Nytt kodexempel – Gissa ett tal

Gosbust finns i ny version (0.9) och med ett nytt exempelprogram. Följande fungerar med version 0.9 eller senare av Gosbust som finns att ladda ner här.

0>>X
/* Trolla fram ett slumptal mellan 1 och 100. */
RAND C C%100>>C C++

Restart:
/* Fråga användaren efter ett tal mellan 1 och 100. */
<<"Skriv ett tal mellan 1 och 100."
X++ ->A$ A$>>A>>I? !I? GOTO I? Restart

/* Sätt flaggor som beskriver om gissningen
var rätt, för liten eller för stor. */
A>C>>S? A<C>>L? A=C>>C?

GOSB S? TooHigh
GOSB L? TooLow
GOTO C? Correct
GOTO 1 Restart

(TooHigh <<"För stort!" )
(TooLow <<"För litet!" )
Correct: <<"Rätt!" <<X

guess

Om du vill fuska, komplettera med <<C på raden före Restart: så ser du det rätta svaret på skärmen.

Vad ska språket heta?

Uppdatering 2015-05-02: Språket kommer att heta Gosbust – ett genialt namn med referenser både till andra språk, populärkultur och till nyckelord i detta språk. Upphovsmannen till namnet är anonym, men har fler fyndigheter på sin meritlista, t.ex. ordet som bättre än något annat beskriver kristendomens gud Jahve – UMO (Undetectable Mythological Object).

Uppdatering 2015-05-03: Under ett tidigt stadium av språkets utveckling så skrev man A>>1 för att lagra 1 i variabeln A. Nu när Gosbust går att ladda hem är detta ändrat. Numera skriver man istället 1>>A för att lagra 1 i variabeln A. Uttrycket A>>1 ger ett kompileringsfel idag. Detta gäller för alla datatyper, och även för de underliggande instruktionerna. PUTI A 1 ska nu skrivas PUTI 1 A, där 1 är värdet som ska lagras och A är variabeln den ska lagras i.

Först och främst vill jag betona att detta språk är lite av ett terapiarbete för mig – det kommer absolut inte lösa världsproblemen eller frälsa några programmerare. Parsern är inte hierarkisk och upplägget är inspirerat av Assembler på Commodore 128. Detta innebär att språket består av instruktioner, där varje instruktion alltid tar samma antal argument. Det innebär att man inte behöver ha olika separatorer mellan argumenten och mellan programsatserna. Om ett kommando tar två argument kommer kompilatorn att läsa två argument och sedan anta att det nästa som påträffas är nästa kommando. Som separator används whitespace (blanksteg, tabb eller enterslag), och det går bra att infoga flera whitespaces efter tycke, men endast på de platser där det ska vara whitespaces. Det går inte att undvika whitespaces där whitespaces förväntas.

Följande kod placerar värdet 1 i heltalsvariabeln A, följt av en kommentar:

PUTI A 1 /* Sparar 1 i A */

Detta däremot kommer inte att fungera eftersom uttrycket 1/* inte är en giltig heltalskonstant:

PUTI A 1/* Sparar 1 i A */

Notera att variablernas datatyp härleds från deras namn. Variabler utan postfix, t.ex. A, B eller C antas vara 32-bitars heltal. Variabler vars namn slutar med $, t.ex. A$, B$ eller C$ antas vara strängar. Frågetecken betyder bit (t.ex. A?) och korsbrygga betyder flyttal (t.ex. A#).

Det finns ett enkelt syntaxlager ovanpå instruktionerna som syftar till att öka läsbarheten. PUTI A 1 kan kortas ner till 1>>A. Detta program visar de 20 första talen i Fibonacci-sekvensen (utom 0):

PUTI 1 A
GETI A
PUTI 1 B
GETI B
PUTI 0 X
PROC Run
   ADDI A B C
   GETI C
   INCI X
   SWPI A B
   SWPI B C
   LETI X 18 J?
   GOTO J? Run
GETS "Done!"

Första raden placerar 1 i variabeln A. GETI A presenterar värdet av A. PROC Run markerar starten av en subrutin som kort och gott heter Run – subrutiner kan heta vad som helst, men om namnet består av något annat än bokstäverna A-Z måste namnet kapslas in av citattecken. Subrutiner körs även om de inte blir anropade, så efter PUTI X 0 (rad 5) kommer ADDI A B C (rad 7) att köras (fallthrough). ADDI A B C sparar summan av A och B i C, vilket kan kortas ner till A+B>>C. INCI X ökar X med 1, vilket kan kortas ner till X++. SWAPI A B låter värdet av A hamna i B och värdet av B hamna i A, vilket kan kortas ner till A<>B. LETI X 18 J? testar huruvida X är mindre än 18, och lagrar i så fall 1 i J?, annars 0. Detta kan kortas ner till X<18>>J?. Motsvarigheten heter GRTI. GOTO J? Run anropar subrutinen Run (i detta fall rekursivt) om värdet av J? är 1.

Så här ser den korta versionen av mitt lilla Fibonacci-program ut:

1>>A <<A 1>>B <<B 0>>X
Run: A+B>>C <<C X++ A<>B B<>C X<18>>J?
GOTO J? Run
<<"Done!"

Apropå anrop så finns två nyckelord för att anropa subrutiner: GOTO och GOSB. Skillnaden mellan dessa är att GOTO egentligen bara ändrar nuvarande exekveringspunkt, medan GOSB (“go sub”) dessutom lagrar vilken plats man lämnar i en enkel callstack. Subrutiner som anropas med GOSB kan lämna tillbaka kontrollen till den anropande koden genom instruktionen RTRN (“return”) som plockar ett värde från callstacken. Om RTRN används när callstacken är tom avslutas programmet.

Trots att språket varken är färdigt eller släppt så finns redan konceptet som en Ruby DSL. Tack Jonas Elfström!

Jag återkommer med fler kodsnuttar, och tar tacksamt emot tips på namn! (Dawkins är upptaget eftersom eftersom den berömde biologen Richard Dawkins faktiskt också har designat ett programmeringsspråk.)

Uppdatering: Tidigare idag publicerade Roger Alsing en Fizz Buzz på Facebook. Finessen med hans Fizz Buzz var att den saknade tester, utan utnyttjade objektorienteringen i C# för att få till serien. Mitt språk är tyvärr inte objektorienterat, men så här ser en fungerande Fizz Buzz ut:

1>>I
Run:
   1>>C? I%3>>F F=0>>F? I%5>>B B=0>>B? F?&B?>>X?
   GOSB X? FizzBuzz
   GOSB B? Buzz
   GOSB F? Fizz
   GOSB C? NoFizzBuzz
   I++ I<101>>J?
   GOTO J? Run
/* Quit! */
RTRN

/* Sub routines... */
FizzBuzz: 0>>F? 0>>B? 0>>C? <<"Fizz Buzz" RTRN
Buzz: 0>>F? 0>>C? <<"Buzz" RTRN
Fizz: 0>>C? <<"Fizz" RTRN
NoFizzBuzz: <<I RTRN

Sci-Fi World

Det är ännu inte för sent att besöka Sci-Fi World i Stockholm, som har öppet även imorgon söndag. Jag spenderade lördagen där, och nördade loss rejält bland Magic-kort och D&D-tärningar. Väldigt mycket kretsade kring Stjärnornas Krig. Dragplåstren var Robert Patrick, Ray Park, Samantha Fox, Joe Cantillo och Sam Jones.

Sam J. Jones spelade Flash Gordon 1980 (som Queen gjorde soundtracket till) och sig själv i filmen Ted.

WP_20150425_008

Detta är bilen Kit från tv-serien Knight Rider med David Hasselhoff:

WP_20150425_027

Har tar min dotter kort på mig och Samantha Fox (som sjöng in en låt till Terror på Elm Street 5):

WP_20150425_011

Detta är Robert Patrick som spelade T-1000 i filmen Terminator 2 och John Doggett i Arkiv X:

WP_20150425_019

Betydelsen av ordet “nörd” är justerat

Mattias Boström twittrade (följ!) detta igår:

“Nörd” har fått ändrad definition i nya SAOL-upplagan (upptill på bilden).

Han bifogar två bilder föreställande den tidigare och den reviderade definitionen från Svenska Akademiens ordlista, där den tidigare lyder “enkelspårig och löjeväckande person, tönt” och den nya lyder “person som har ett stort specialintresse och som därför kan verka något enkelspårig“.

Eventuellt kan min syn på ordet vara färgad av hur vi använde ordet på 80-talet eftersom jag knappt använt ordet sedan dess, men då var “nörd” svengelska för “nerd” som fortfarande är en nedsättande beskrivning av någon som är lite töntig. Men de som beskriver sig själva som nördar idag, väljer alltså inte ett negativt ord för att påvisa självdistans, utan ett ord som betraktas som relativt neutralt.

Den som vill ta sig an kulturskatten av college- och high school-filmer från 80-talet kommer fortfarande komma i kontakt med “nördar” i ordets gamla bemärkelse, men dessa kanske vi ska benämna som “nerdar” idag för att visa kopplingen till det engelska ordet “nerd”.

Vad sägs om höjdarfilmen “Nördarna kommer!” (Revenge of the nerds) från 1985:

Eller klassikern “Drömtjejen” (Weird Science), också från 1985:

Mycket nöje!

Kom igång med C128 Assembler

Idag finns en hel del produkter för att utveckla program till Commodore 64 eller 128 på en modern PC, vilket är betydligt enklare än att göra det direkt på målmaskinen. Följ dessa steg för att komma igång med Assembler i Commodore 128.

1. Ladda hem Vice. Vice emulerar de flesta Commodore-maskinerna, vilket förutom C64/C128 även innefattar Pet, Vic 20 och Commodore +4.

2. Ladda hem CBM .prg Studio. Detta är en fullfjädrad integrerad utvecklingsmiljö för valfri Commodore-maskin.

3. Starta CBM .prg Studio. Välj Tools -> Options. Klicka på fliken Emulator Control. Bocka för C128 och peka C128-emulatorn från Vice (filen x128.exe).

4. Välj File -> New Project. Kryssa i C128, klicka Next. Vid Project Location, klicka Browse och välj var du vill spara din källkod. Därefter, klicka Next och Create.

5. I fönstret Project Explorer, högerklicka på Assembly Files och välj Add New File. Tillhandahåll ett filnamn med filändelsen .asm.

6. Det sista du måste göra för att komma igång, är att ange ditt programs startadress. Först i din .asm-fil, skriv:

*=$2000

Symbolen * representerar programmets start och $2000 är den hexadecimala representationen av 8192. Detta betyder alltså att ditt program startas med SYS 8192. När du väl börjar koda ditt program måste du tänka på att CBM .prg Studio antar att en indragning betyder att du vill göra en programsats. Låt säga att du vill förändra borderfärgen vars minnesadress är 53280 (D020 i hex), kan du skriva följande program (och notera indragningarna):

*=$2000
        inc $D020
        brk

Instruktionen inc ökar värdet med 1 på angiven minnesadress och brk avbryter programmet. Nu har du ett fungerande program. Öppna programmet genom att trycka på F5. Stäng fönstret som visar disassemblyn (Assembly Dump) och notera hur Vice visar en C128 med ditt program inläst i minnet. Om du vill kontrollera värdet av $D020, skriv:

PRINT PEEK(53280)

Notera värdet som står där, t.ex. 253. Starta ditt program genom att skriva:

SYS 8192

Och direkt därefter, tryck X följt av Enter för att lämna Commodore 128:ans maskinkodsmonitor. Eftersom programmet ökar värdet av 53280 med 1, borde borderfärgen nu vara ljusblå, eftersom ljusblå ligger som nästa färg efter ljusgrön. Och mycket riktigt, upprepa PRINT PEEK(53280) och konstatera att värdet har ökat med 1, till t.ex. 254.

bild1

Kör programmet igen genom att skriva SYS 8192 igen. Nu har vi ökat 53280 med 1 igen, vilket ger ljusgrå borderfärg. Om du vill kan du titta på disassemblyn innan du lämnar monitorn genom att skriva D2000 och sedan X.

bild2

När du är färdig kan du stänga emulatorn (eller emulatorerna) – en ny öppnas varje gång du kör programmet.

Färgpaletten i Acorn Electron

Acorn Electron kan förvisso visa 16 olika färger, men kan bara visa 8 olika nyanser. Dessa unika nyanser ligger på position 0 till 7 på färgpaletten.

0 – Svart
1 – Röd
2 – Grön
3 – Gul
4 – Blå
5 – Lila
6 – Turkos
7 – Vit

De resterande färgerna återanvänder dessa nyanser i blinkande form.

8 – Blinkande svart/vit
9 – Blinkande röd/turkos
10 – Blinkande grön/lila
11 – Blinkande gul/blå
12 – Blinkande blå/gul
13 – Blinkande lila/grön
14 – Blinkande turkos/röd
15 – Blinkande vit/svart

För att se hur blinkande svart/vit ser ut bredvid blinkande vit/svart, testa gärna detta program:

10 MODE 1
20 VDU 19, 0, 0, 0, 0, 0
30 VDU 19, 1, 8, 0, 0, 0
40 VDU 19, 2, 15, 0, 0, 0
50 GCOL 0, 1
60 MOVE 50, 50
70 DRAW 100, 50
80 GCOL 0, 2
90 MOVE 60, 60
100 DRAW 110, 60

Rad 10 anger 320 * 256 pixlar med fyra färger. Rad 20 anger att den virtuella färgen 0 betyder svart. Rad 30 anger att den virtuella färgen 1 betyder blinkande svart/vit. Rad 40 anger att den virtuella färgen 2 betyder blinkande vit/svart. Därefter (rad 50-70) ritas en kort horisontell linje i undre vänstra hörnet med färg 1 (blinkande svart/vit). Sist (rad 80-100) ritas en till kort horisontell linje strax ovanför till höger. Detta ger illusionen att linjen hoppar fram och tillbaka, eftersom när den ena är vit är den andra svart.

Den första nollan i GCOL (rad 50 eller rad 80) betyder att pixlarna ska ritas, men genom att sätta andra värden där, kan man använda logiska operationer på pixlarna, som t.ex. OR, AND, EOR eller INVERT. Den andra siffran i GCOL är val av logisk färg.

För att få bort de blinkande linjerna, rensa grafikminnet med kommandot CLG.

Grafiklägen i Acorn Electron

Vissa av er kanske minns Acorn Electron från 1983. På den tiden var PC-datorer både relativt dyra och oförmögna att visa grafik. Företaget Acorn hade en billigare maskin i sortementet, Electron, som kunde avsätta 20 kb för grafik, vilket kunde innebära monokrom grafik 640 * 256 pixlar, eller så mycket som 16 färger i lågupplöst läge (160 * 256 pixlar).

Acorn Electron  - Foto: Oscar Orallo

Acorn Electron – Foto: Oscar Orallo

Den som vill programmera sin Electron kan använda den inbyggda BBC Basic-tolken, där kommandot MODE 0 ger monokrom högupplöst grafik (första bilden) och MODE 2 ger lågupplöst grafik med 16 färger (andra bilden). Den som förväntar sig sexton olika nyanser kommer att bli besviken, och bör välja ett läge med färre färger – Electron kan bara visa 8 nyanser.

bild1

bild2

För den som inte är beredd offra tjugotusen bytes för grafik kan använda sin Electron i text only-läge (40 * 25 tecken) för endast 8 k genom att skriva MODE 6. Givet att du väljer läge 2 så kan du använda alla 16 färger, vilket tyvärr handlar om svart (0) röd (1), grön, gul, blå, lila, turkos och vit (7), samt blinkande svart/vit (8), röd/cyan (9), grön/lila, gul/blå och deras omvända (12-15). Om du inte tror mig, bör du köra detta program som använder 320 * 256 pixlar med 4 färger (läge 1):

10 MODE 1
20 VDU 19, 0, 9
30 PRINT "NU BLINKAR ROD OCH CYAN"
40 PRINT "TRYCK ENTER."
50 INPUT A$
60 VDU 19, 0, 14
70 PRINT "NU BLINKAR CYAN OCH ROD."

Inte nog med att detta program illustrerar det bisarra med att avsätta minne för bit-kombinationer som egentligen inte är speciellt unika, programmet belyser dessutom något som ser ut som en bugg.

Kommandot PRINT “TJOHEJ” ger utdatat TJOHEJ, såvida det inte föregås av VDU 19 – då tappar Electron bort tre bytes och ger HEJ, vilket illustreras av följande program, som förutom en massa vackra färger ger TJOHEJ och HEJ som utdata:

10 PRINT "TJOHEJ"
20 VDU 19, 0, 2
30 PRINT "TJOHEJ"

bild3

Detta beror på att även om du inte behöver fler parametrar, lyssnar VDU 19 efter ytterligare tre bytes, och tar vad den kan få. Man löser problemet t.ex. genom att fylla upp med nollor.

10 PRINT "TJOHEJ"
20 VDU 19, 0, 2, 0, 0, 0
30 PRINT "TJOHEJ"

Ovanstående kod ger verkligen TJOHEJ följt av TJOHEJ.

Word 5.5 under DosBox

DosBox är en DOS-emulator som fungerar under bl.a. Windows 8.1 som ger utmärkt stöd för att köra DOS-program i fönster eller i helskärmsläge (växla med Alt+Enter). Jag tror att det primära syftet är att kunna spela de gamla favoritspelen från 90-talet på sin moderna PC, men emulatorn öppnar även för andra tillämpningar.

Jag laddade hem Microsoft Word 5.5 för DOS, och placerade den packade filen i en undermapp till mappen jag använder som hårdisk i DOS, i mitt fall C:\DosBox\Word\word.exe. Tänk på att DosBox ska vara startad när du ändrar i filsystemet.

Med filen på plats kan man starta DosBox och göra mappen till sin C-disk genom följande kommando:

MOUNT C C:\DosBox

Sedan är det bara att skriva C: för att komma dit, och påbörja installationen. Installationen sker i två steg. Först ska Wd55_ben.exe köras, så att filerna packas upp. Därefter ska installationsprogrammet köras. Låt inte installationsprogrammet ändra i några inställningar eftersom DosBox redan styr upp allt som behöver styras upp.

Efter installationen märkte jag att muspekaren inte reagerade som den skulle, men efter att ha avslutat Word och DosBox, och sedan startat dem igen, har jag inte märkt av det felet. Sen är det bara att flyga in i inställningarna och göra önskvärda inställningar, och börja skriva. Mycket nöje!

För att hitta länken till installationsfilen Wd55_ben.exe, besök denna sida och sök reda på textstycket “If you just want to get your free Word for DOS, click this link and the almighty Microsoft download deity will cause it to appear on your PC”.

Sprite collision detection

The Commodore 128 has built in sprite collision detection, but since Commodore Basic 7.0 is so slow, it must be used with some care. To demonstrate this, I have used SPRDEF to create two masterpieces. This is sprite 1:

s1

And this is sprite 2, this is excellent:

s2

I want to demonstrate the level of detail of the collision detection by turning both sprites on at an overlapping position, but not and colliding position, and then read out the collision flags. If you are doing this on your own Commodore 128, you might want to adjust the positions if your sprites are not identical to mine.

s3

The image shows that the sprites share an overlapping position, but they do not collide. There are no overlapping pixels. Yet, the BUMP(1) function will return 3 in this situation. The value 3 means that flag 1 and 2 are turned on, indicating that sprite 1 and  2 are colliding, even though they just share position. However, the size of the sprite is determined by the smallest rectangle that include all pixels of the sprite, which is second best after pixel perfect collision detection.

Game programming in Commodore Basic 7 is futile. Despite this cheating, the sprites moves faster than the Basic interpreter can handle. To see this, you can make the sprites move towards each other, and their positions if they collide. You should see that not all collisions are acted upon.

s4

Actual fullscreen text-console on Windows 7/8

You can’t put the old CMD.EXE in fullscreen anymore, but there is replacement called Console2 that has some cool features. Do not set Console2 as your standard console in Windows you use shortcuts like the Visual Studio Command Prompt, because the different behaviors will set Console2 in an infinite loop.

When installed, you can open the Settings window to tweak appearance, like hide the scrollbars and such. When done, you toggle fullscreen mode using the F11 key. This is what you get:

fullscreen

Update 2014-08-23: The latest version with the fullscreen option is 1.8.0. I am hosting it here.

Commodore 128 bitmap graphics 2/2

Part 1. Part 2.

On the Commodore 128, the screen is divided into characters.

1

Each character consists of eight eight-pixel lines (bytes), which make a total of 64 pixels per character.

2

Each pixel pair represent one of four available colors in multicolor mode. This can be demonstrated using this simple code:

10 GRAPHIC 3, 1 : REM CREATE CLEAN MULTICOLOR DISPLAY
20 DRAW 1, 0, 0 : REM DRAW USING COLOR 1
30 DRAW 2, 0, 1 : REM DRAW USING COLOR 2
40 DRAW 3, 0, 2 : REM DRAW USING COLOR 3

Now, this pattern is shown:

3

If you add a line of code to change to high resolution graphics without clearing, we can see that two turned off pixels represent the first color, one off and one on represent color 2, one on and one off represent color 3 and two turned on pixels represent color 4.

50 GRAPHIC 1, 0 : REM CHANGE TO HIGH RESOLUTION WITHOUT CLEARING

4

The graphics memory is movable. This command places the graphics memory at 2000 (8192 in decimal):

POKE 53272, (PEEK(53272) AND 240) OR 8

The eight pixel pattern of each row in a character can be described using binary 00000000 for all off to 11111111 for all on (0 to 255 in decimal). Placing the number 00011011 (27) will draw one multicolor pixel of each color on the first line. 00 is color 0 (background), 01 is color 1, 10 is color 2 and 11 is color 3.

10 GRAPHICS 3, 1
20 POKE 53272, (PEEK(53272) AND 240) OR 8
30 POKE 8192, 27

5

Commodore 128 bitmap graphics 1/2

Part 1. Part 2.

When Commodore 128 vector graphics can’t provide the desired details, there is an option to do bitmap graphics. The 128 have a built in command for creating pixel perfect graphics called SPRSAV. You can create a pattern using the built-in editor (SPRDEF)…

01

…and transfer the data into a string variable, and then copy that data to the screen, like so:

10 COLOR 0, 13
20 COLOR 4, 13
30 COLOR 1, 1
40 COLOR 2, 8
50 COLOR 3, 12
60 GRAPHIC 3, 1
70 SPRSAV 1, A$
80 GSHAPE A$, 10, 10, 2 : REM DRAW SPRITE AT 10, 10
90 GSHAPE A$, 20, 11, 2
100 GSHAPE A$, 30, 12, 2

02

This should be enough for anyone, but if you like, you can also access the graphics memory directly.