{"id":2396,"date":"2019-12-14T22:26:27","date_gmt":"2019-12-14T19:26:27","guid":{"rendered":"http:\/\/demensdeum.com\/blog\/?p=2396"},"modified":"2024-12-16T22:32:32","modified_gmt":"2024-12-16T19:32:32","slug":"%d1%80%d0%b0%d0%b7%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%ba%d0%b0-%d0%b8%d0%b3%d1%80%d1%8b-%d0%b4%d0%bb%d1%8f-zx-spectrum-%d0%bd%d0%b0-c","status":"publish","type":"post","link":"https:\/\/demensdeum.com\/blog\/pt\/2019\/12\/14\/%d1%80%d0%b0%d0%b7%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%ba%d0%b0-%d0%b8%d0%b3%d1%80%d1%8b-%d0%b4%d0%bb%d1%8f-zx-spectrum-%d0%bd%d0%b0-c\/","title":{"rendered":"Desenvolvimento de jogos para ZX Spectrum em C"},"content":{"rendered":"<p>Esse post bobo \u00e9 dedicado ao desenvolvimento de um jogo para o antigo computador ZX Spectrum em C. Vamos dar uma olhada no bonit\u00e3o:<\/p>\n<p><a href=\"https:\/\/www.flickr.com\/photos\/quenerapu\/2622099393\" target=\"_blank\" rel=\"noopener noreferrer\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2398\" src=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2019\/12\/2622099393_8199976c15_w.jpg\" alt=\"\" width=\"400\" height=\"294\" srcset=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2019\/12\/2622099393_8199976c15_w.jpg 400w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2019\/12\/2622099393_8199976c15_w-300x221.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>Iniciou a produ\u00e7\u00e3o em 1982 e foi produzido at\u00e9 1992. Caracter\u00edsticas t\u00e9cnicas da m\u00e1quina: processador Z80 de 8 bits, 16-128kb de mem\u00f3ria e outras extens\u00f5es, como o <b>chip de som AY-3-8910.<\/b><\/p>\n<p>Como parte da competi\u00e7\u00e3o <a href=\"http:\/\/rgb.yandex\/\" target=\"_blank\" rel=\"noopener noreferrer\">Yandex Retro Games Battle 2019<\/a> para esta m\u00e1quina, escrevi um jogo chamado Interceptor 2020. Como n\u00e3o tive tempo de aprender linguagem assembly para o Z80, resolvi desenvolv\u00ea-lo em C. Como conjunto de ferramentas, escolhi um conjunto pronto &#8211; z88dk, que cont\u00e9m compiladores C e diversas bibliotecas auxiliares para agilizar a implementa\u00e7\u00e3o de aplica\u00e7\u00f5es para o Spectrum. Ele tamb\u00e9m suporta muitas outras m\u00e1quinas Z80, como MSX e calculadoras Texas Instruments.<\/p>\n<p>A seguir, descreverei meu voo superficial sobre a arquitetura do computador, o conjunto de ferramentas z88dk, e mostrarei como consegui implementar a abordagem OOP e usar padr\u00f5es de design.<\/p>\n<h3>Recursos de instala\u00e7\u00e3o<\/h3>\n<p>A instala\u00e7\u00e3o do z88dk deve ser realizada de acordo com o manual do reposit\u00f3rio, por\u00e9m, para usu\u00e1rios do Ubuntu, gostaria de destacar um recurso &#8211; Se voc\u00ea j\u00e1 possui compiladores para Z80 instalados a partir de pacotes deb, ent\u00e3o voc\u00ea deve remov\u00ea-los, j\u00e1 que z88dk os acessar\u00e1 da pasta bin por padr\u00e3o devido \u00e0 incompatibilidade das vers\u00f5es do compilador do conjunto de ferramentas, voc\u00ea provavelmente n\u00e3o conseguir\u00e1 compilar nada.< \/p><\/p>\n<h3>Ol\u00e1, mundo<\/h3>\n<p>Escrever Hello World \u00e9 muito f\u00e1cil:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>#include \n\nvoid main()\n{\n    printf(\"Hello World\");\n}\n<\/code><\/pre>\n<\/div>\n<p>\u00c9 ainda mais f\u00e1cil compilar um arquivo tap:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>zcc +zx -lndos -create-app -o helloworld helloworld.c\n<\/code><\/pre>\n<\/div>\n<p>Para rodar, use qualquer emulador ZX Spectrum com suporte a arquivo tap, por exemplo online:<br \/><a href=\"http:\/\/jsspeccy.zxdemo.org\/\">http:\/\/jsspeccy.zxdemo.org\/<\/a><\/p>\n<h3>Desenhe na imagem em tela cheia<\/h3>\n<p><i>tl;dr As imagens s\u00e3o desenhadas em blocos, blocos de tamanho 8&#215;8 pixels, os pr\u00f3prios blocos s\u00e3o incorporados na fonte Spectrum e, em seguida, a imagem \u00e9 impressa como uma linha a partir dos \u00edndices. <\/i><\/p>\n<p>A biblioteca de sa\u00edda de sprites e blocos sp1 produz blocos usando UDG. A imagem \u00e9 traduzida em um conjunto de UDGs (blocos) individuais e depois montados na tela usando \u00edndices. Deve ser lembrado que UDG \u00e9 usado para exibir texto, e se sua imagem contiver um conjunto muito grande de blocos (por exemplo, mais de 128 blocos), voc\u00ea ter\u00e1 que ir al\u00e9m dos limites do conjunto e apagar o Spectrum padr\u00e3o fonte. Para contornar essa limita\u00e7\u00e3o, utilizei uma base de 128 &#8211; 255 simplificando as imagens, deixando a fonte original no lugar. Sobre simplificar as imagens abaixo.<\/p>\n<p>Para desenhar imagens em tela cheia voc\u00ea precisa se munir de tr\u00eas utilit\u00e1rios:<br \/>Gimp<br \/>img2spec<br \/>png2c-z88dk<\/p>\n<p>Existe uma maneira para verdadeiros homens ZX, verdadeiros guerreiros retr\u00f4, \u00e9 abrir um editor gr\u00e1fico usando a paleta Spectrum, conhecendo os recursos de sa\u00edda da imagem, prepar\u00e1-la manualmente e carreg\u00e1-la usando png2c-z88dk ou png2scr.< \/p><\/p>\n<p>A maneira mais f\u00e1cil &#8211; pegue uma imagem de 32 bits, mude o n\u00famero de cores para 3-4 no Gimp, edite-a levemente e importe-a para img2spec para n\u00e3o trabalhar com restri\u00e7\u00f5es de cores manualmente, exporte png e converta-a em um array C usando png2c- z88dk.<\/p >\n<p>Lembre-se de que, para uma exporta\u00e7\u00e3o bem-sucedida, cada bloco n\u00e3o pode conter mais de duas cores.<\/p>\n<p>Como resultado, voc\u00ea receber\u00e1 um arquivo h que cont\u00e9m o n\u00famero de blocos \u00fanicos, se houver mais de ~128, simplifique a imagem no Gimp (aumente a repetibilidade) e execute o procedimento de exporta\u00e7\u00e3o sobre um novo .<\/p>\n<p>Ap\u00f3s a exporta\u00e7\u00e3o, voc\u00ea literalmente carrega a \u201cfonte\u201d dos blocos e imprime o \u201ctexto\u201d dos \u00edndices dos blocos na tela. Abaixo est\u00e1 um exemplo da \u201cclasse\u201d de renderiza\u00e7\u00e3o:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\/\/ \u0433\u0440\u0443\u0437\u0438\u043c \u0448\u0440\u0438\u0444\u0442 \u0432 \u043f\u0430\u043c\u044f\u0442\u044c\n    unsigned char *pt = fullscreenImage->tiles;\n\n    for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {\n            sp1_TileEntry(fullscreenImage->tilesBase + i, pt);\n    }\n\n    \/\/ \u0441\u0442\u0430\u0432\u0438\u043c \u043a\u0443\u0440\u0441\u043e\u0440 \u0432 0,0\n    sp1_SetPrintPos(&ps0, 0, 0);\n\n    \/\/ \u043f\u0435\u0447\u0430\u0442\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443\n    sp1_PrintString(&ps0, fullscreenImage->ptiles);\n<\/code><\/pre>\n<\/div>\n<h3>Desenhando sprites na tela<\/h3>\n<p>A seguir descreverei um m\u00e9todo para desenhar sprites de 16\u00d716 pixels na tela. N\u00e3o cheguei na anima\u00e7\u00e3o e na mudan\u00e7a de cores, porque&#8230; \u00c9 trivial que j\u00e1 nesta fase, presumo, tenha ficado sem mem\u00f3ria. Portanto, o jogo cont\u00e9m apenas sprites monocrom\u00e1ticos transparentes.<\/p>\n<p>Desenhamos uma imagem png monocrom\u00e1tica 16&#215;16 no Gimp, depois usando png2sp1sprite a traduzimos em um arquivo assembly asm, em c\u00f3digo C declaramos arrays do arquivo assembly e adicionamos o arquivo na fase de montagem.< \/p><\/p>\n<p>Ap\u00f3s a etapa de declara\u00e7\u00e3o do recurso sprite, ele deve ser adicionado na tela na posi\u00e7\u00e3o desejada, segue abaixo um exemplo do c\u00f3digo da \u201cclasse\u201d do objeto do jogo:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>    struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);\n    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, col2-col1, 0);\n    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB,  SP1_TYPE_2BYTE, 0, 0);\n    sp1_IterateSprChar(bubble_sprite, initialiseColour);\n<\/code><\/pre>\n<\/div>\n<p>A partir dos nomes das fun\u00e7\u00f5es voc\u00ea pode entender aproximadamente o significado de &#8211; aloque mem\u00f3ria para o sprite, adicione duas colunas 8&#215;8, adicione uma cor para o sprite.<\/p>\n<p>A posi\u00e7\u00e3o do sprite \u00e9 indicada em cada quadro:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);\n<\/code><\/pre>\n<\/div>\n<h3>Emulando OOP<\/h3>\n<p>N\u00e3o existe sintaxe para OOP em C. O que voc\u00ea deve fazer se ainda quiser? Voc\u00ea precisa conectar sua mente e ser iluminado pela ideia de que n\u00e3o existe equipamento OOP; em \u00faltima an\u00e1lise, tudo se resume a uma das arquiteturas de m\u00e1quina, na qual simplesmente n\u00e3o existe o conceito de objeto e outras abstra\u00e7\u00f5es associadas a ele.< \/p><\/p>\n<p>Esse fato me impediu por muito tempo de entender por que OOP \u00e9 necess\u00e1rio, por que \u00e9 necess\u00e1rio us\u00e1-lo se no final tudo se trata de c\u00f3digo de m\u00e1quina.<\/p>\n<p>Por\u00e9m, ap\u00f3s trabalhar no desenvolvimento de produtos, descobri as del\u00edcias desse paradigma de programa\u00e7\u00e3o, principalmente, \u00e9 claro, flexibilidade de desenvolvimento, mecanismos de prote\u00e7\u00e3o de c\u00f3digo, com abordagem correta, reduzindo entropia, simplificando o trabalho em equipe. Todos os benef\u00edcios acima decorrem de tr\u00eas pilares &#8211; polimorfismo, encapsulamento, heran\u00e7a.<\/p>\n<p>Vale ressaltar tamb\u00e9m a simplifica\u00e7\u00e3o da resolu\u00e7\u00e3o de quest\u00f5es relacionadas \u00e0 arquitetura de aplica\u00e7\u00f5es, pois 80% dos problemas de arquitetura foram resolvidos por cientistas da computa\u00e7\u00e3o no s\u00e9culo passado e descritos na literatura sobre padr\u00f5es de projeto. A seguir, descreverei maneiras de adicionar sintaxe semelhante a OOP em C.<\/p>\n<p>\u00c9 mais conveniente tomar estruturas C como base para armazenar dados de uma inst\u00e2ncia de classe. Claro, voc\u00ea pode usar um buffer de bytes, criar sua pr\u00f3pria estrutura para classes, m\u00e9todos, mas por que reinventar a roda? Afinal, j\u00e1 estamos reinventando a sintaxe.<\/p>\n<h4>Dados da turma<\/h4>\n<p>Exemplo de campos de dados de \u201cclasse\u201d do GameObject:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>struct GameObjectStruct {\n    struct sp1_ss *gameObjectSprite;\n    unsigned char *sprite_col;\n    unsigned char x;\n    unsigned char y;\n    unsigned char referenceCount;\n    unsigned char beforeHideX;\n    unsigned char beforeHideY;\n};\ntypedef struct GameObjectStruct GameObject;\n<\/code><\/pre>\n<\/div>\n<p>Salve nossa classe como \u201cGameObject.h\u201d, fa\u00e7a #include \u201cGameObject.h\u201d no lugar certo e use-a.<\/p>\n<h4>M\u00e9todos de classe<\/h4>\n<p>Levando em considera\u00e7\u00e3o a experi\u00eancia dos desenvolvedores da linguagem Objective-C, a assinatura de um m\u00e9todo de classe ser\u00e3o fun\u00e7\u00f5es em escopo global, o primeiro argumento ser\u00e1 sempre a estrutura de dados, seguido dos argumentos do m\u00e9todo. Abaixo est\u00e1 um exemplo de \u201cm\u00e9todo\u201d da \u201cclasse\u201d GameObject:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>void GameObject_hide(GameObject *gameObject) {\n    gameObject->beforeHideX = gameObject->x;\n    gameObject->beforeHideY = gameObject->y;\n    gameObject->y = 200;\n}\n<\/code><\/pre>\n<\/div>\n<p>A chamada do m\u00e9todo \u00e9 semelhante a esta:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>GameObject_hide(gameObject);\n<\/code><\/pre>\n<\/div>\n<p>Construtores e destruidores s\u00e3o implementados da mesma maneira. \u00c9 poss\u00edvel implementar um construtor como alocador e inicializador de campo, mas prefiro controlar a aloca\u00e7\u00e3o e inicializa\u00e7\u00e3o de objetos separadamente.<\/p>\n<h4>Trabalhando com mem\u00f3ria<\/h4>\n<p>Gerenciamento manual de mem\u00f3ria do formul\u00e1rio usando malloc e free agrupados em macros novas e de exclus\u00e3o para corresponder ao C++:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>#define new(X) (X*)malloc(sizeof(X))\n#define delete(X) free(X)\n<\/code><\/pre>\n<\/div>\n<p>Para objetos que s\u00e3o usados \u200b\u200bpor v\u00e1rias classes ao mesmo tempo, o gerenciamento de mem\u00f3ria semi-manual \u00e9 implementado com base na contagem de refer\u00eancias, \u00e0 imagem e semelhan\u00e7a do antigo mecanismo ARC do Objective-C Runtime:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>void GameObject_retain(GameObject *gameObject) {\n    gameObject->referenceCount++;\n}\n\nvoid GameObject_release(GameObject *gameObject) {\n    gameObject->referenceCount--;\n\n    if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);\n        sp1_DeleteSpr(gameObject->gameObjectSprite);\n        delete(gameObject);\n    }\n}\n<\/code><\/pre>\n<\/div>\n<p>Assim, cada classe deve declarar o uso de um objeto compartilhado usando reter, liberando propriedade por meio de release. A vers\u00e3o moderna do ARC usa chamadas autom\u00e1ticas de reten\u00e7\u00e3o\/libera\u00e7\u00e3o.<\/p>\n<h3>Som!<\/h3>\n<p>O Spectrum possui um tweeter capaz de reproduzir m\u00fasica de 1 bit; os compositores da \u00e9poca conseguiam reproduzir nele at\u00e9 4 canais de som simultaneamente.<\/p>\n<p>O Spectrum 128k cont\u00e9m um chip de som separado AY-3-8910, que pode reproduzir m\u00fasica rastreada.<\/p>\n<p>Uma biblioteca \u00e9 oferecida para usar o tweeter no z88dk<\/p>\n<h3>O que resta aprender<\/h3>\n<p>Eu estava interessado em conhecer o Spectrum, implementar o jogo usando o z88dk e aprender muitas coisas interessantes. Ainda tenho muito que aprender, por exemplo, o montador Z80, pois ele me permite usar toda a pot\u00eancia do Spectrum, trabalhar com bancos de mem\u00f3ria e trabalhar com o chip de som AY-3-8910. Espero participar da competi\u00e7\u00e3o no pr\u00f3ximo ano!<\/p>\n<h3>Links<\/h3>\n<p><a href=\"https:\/\/rgb.yandex\/\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/rgb.yandex<\/a><br \/><a href=\"https:\/\/vk.com\/sinc_lair\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/vk.com\/sinc_lair<\/a><br \/>\n<a href=\"https:\/\/www.z88dk.org\/forum\/\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/www.z88dk.org\/forum\/<\/a><\/p>\n<h3>C\u00f3digo fonte<\/h3>\n<p><a href=\"https:\/\/gitlab.com\/demensdeum\/zx-projects\/tree\/master\/interceptor2020\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/gitlab.com\/demensdeum\/ zx-projects\/tree\/master\/interceptor2020<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Esse post bobo \u00e9 dedicado ao desenvolvimento de um jogo para o antigo computador ZX Spectrum em C. Vamos dar uma olhada no bonit\u00e3o: Iniciou a produ\u00e7\u00e3o em 1982 e foi produzido at\u00e9 1992. Caracter\u00edsticas t\u00e9cnicas da m\u00e1quina: processador Z80 de 8 bits, 16-128kb de mem\u00f3ria e outras extens\u00f5es, como o chip de som AY-3-8910.<a class=\"more-link\" href=\"https:\/\/demensdeum.com\/blog\/pt\/2019\/12\/14\/%d1%80%d0%b0%d0%b7%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%ba%d0%b0-%d0%b8%d0%b3%d1%80%d1%8b-%d0%b4%d0%bb%d1%8f-zx-spectrum-%d0%bd%d0%b0-c\/\">Continue reading <span class=\"screen-reader-text\">&#8220;Desenvolvimento de jogos para ZX Spectrum em C&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[49,81,61],"tags":[134,133],"class_list":["post-2396","post","type-post","status-publish","format-standard","hentry","category-blog","category-demos","category-techie","tag-spectrum","tag-z88dk","entry"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"pt","enabled_languages":["en","ru","zh","de","fr","ja","pt","hi"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"ru":{"title":true,"content":true,"excerpt":false},"zh":{"title":true,"content":true,"excerpt":false},"de":{"title":true,"content":true,"excerpt":false},"fr":{"title":true,"content":true,"excerpt":false},"ja":{"title":true,"content":true,"excerpt":false},"pt":{"title":true,"content":true,"excerpt":false},"hi":{"title":false,"content":false,"excerpt":false}}},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2396","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/comments?post=2396"}],"version-history":[{"count":32,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2396\/revisions"}],"predecessor-version":[{"id":3930,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2396\/revisions\/3930"}],"wp:attachment":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/media?parent=2396"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/categories?post=2396"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/tags?post=2396"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}