Fonte atual: curriculum/arrowfall/checkpoints/02-state · Flecha 0.3.0 · 2/7.
O programa neste checkpoint
Até aqui: O checkpoint 01 imprime uma sala reconhecível e tem uma referência visual exata.
O checkpoint 01 desenha um arqueiro, mas o caractere @ não tem relação com um objeto do jogo. Movê-lo exigiria alterar a linha literal, e a coordenada impressa em outro lugar poderia divergir silenciosamente.
Este checkpoint mantém a sala visualmente igual enquanto substitui essa duplicação por uma única fonte de verdade. Game possui a Position atual; toda representação visível consulta esse valor em Game.
Limitação atual
Trocar um caractere em uma string não expressa movimento. O renderer não tem uma fonte de verdade para a posição do arqueiro.
Próximo comportamento necessário: Manter a mesma sala, mas derivar @ e a coordenada a partir de dados.
Modelo e invariantes
Fazer Game possuir uma Position. O desenho recebe um valor copiado do estado em vez de possuir o jogador.
Position agrupa as duas coordenadas que precisam mudar juntas. Game possui essa posição, e seu método build estabelece as coordenadas iniciais em um só lugar.
Position agrupa x e y porque uma localização só faz sentido quando as duas coordenadas viajam juntas. Game possui uma Position privada, então código externo pode observar a localização, mas não substituir diretamente o campo que pertence ao jogo.
Game::playerPosition recebe uma referência verificada e const para self e retorna a pequena Position por valor. O chamador recebe uma cópia para renderizar; a propriedade do campo autoritativo continua em Game. Essa é a primeira fronteira entre modelo e apresentação.
A renderização percorre as três colunas internas e compara cada coordenada com a posição copiada. Exatamente uma comparação é verdadeira, portanto @ é derivado do estado. A saída continua como regra fixa da última coluna e as demais células viram piso.
O invariante é que Game::start estabelece uma posição inicial válida uma vez. O desenho nunca a altera, e tanto o glifo no mapa quanto a linha de coordenadas usam a mesma cópia. Entrada e mutação continuam fora do escopo.
Implementação
A implementação está dividida nas mudanças abaixo. Cada trecho vem literalmente do checkpoint qualificado e é explicado na parte do fluxo de dados em que participa. Os trechos não são programas independentes; o código completo compilado está disponível depois da execução.
- Introduzir Position e Game.
- Construir o estado com build.
- Renderizar @ comparando a coordenada atual.
Posição própria e invariante inicial
O primeiro trecho define a fronteira de propriedade. Position contém as duas coordenadas; Game contém uma Position privada e é o único dono da localização autoritativa do jogador.
Trecho: src/main.fl:3–28. O arquivo completo aparece na seção de código abaixo.
structure Position { i32 x; i32 y;};
structure Game { private Position player;};
Position Position::at(i32 x, i32 y) { return build Position { self.x = x; self.y = y; };}
Game Game::start() { return build Game { self.player = Position::at(1, 1); };}
Position Game::playerPosition(&const self) { return self->player;}Position::at constrói os dois campos juntos, evitando coordenadas parcialmente inicializadas. Game::start escolhe (1, 1) em um único lugar, sem espalhar a célula inicial pelo código de renderização.
playerPosition empresta Game como const, então observar a localização não pode alterar o modelo. Retornar uma cópia é adequado para esse valor de dois inteiros; uma referência acoplaria o renderer ao armazenamento de Game sem benefício aqui.
Renderização a partir do estado
main obtém um snapshot da posição e o passa para draw_row. O mesmo snapshot produz a coordenada textual, impedindo que as duas saídas consultem estados diferentes.
Trecho: src/main.fl:30–51. O arquivo completo aparece na seção de código abaixo.
state := Game::start(); player := state.playerPosition(); stdoutln("ARROWFALL"); stdoutln("#####"); draw_row(player); stdoutln("#####"); stdoutln("Player: (", player.x, ", ", player.y, ")");}
void draw_row(Position player) { stdout("#"); loop i32 x = 1; x < 4; x++ { if player.x == x and player.y == 1 { stdout("@"); } else if x == 3 { stdout(">"); } else { stdout("."); } } stdoutln("#");}draw_row visita as coordenadas x 1, 2 e 3. O primeiro ramo coloca @ somente quando as duas coordenadas coincidem; o segundo reserva a coluna 3 para a saída; o último preenche a célula restante com piso.
O percurso é pequeno e explícito de propósito. Um array de mapa completo seria desnecessário antes de as paredes virarem dados no checkpoint 04, mas renderizar a partir de coordenadas já estabelece a regra de fonte única mantida pelo renderer posterior.
Execute e examine o resultado
O comando usa a entrada registrada do checkpoint, portanto o comportamento descrito acima pode ser reproduzido sem depender de tempo manual.
longbow run tutorial/checkpoints/02-state < tutorial/checkpoints/02-state/tests/input.txtO frame ainda se parece com o checkpoint 01 e depois imprime Player: (1, 1). Essa linha adicional é uma evidência útil: o glifo @ e a coordenada relatada vieram do mesmo snapshot de Position.
No exercício, alterar Game::start para (2, 1) deve mover as duas representações. Se apenas uma mudar, a renderização reintroduziu uma segunda fonte de verdade.
Transcript acessível do frame
ARROWFALL######@.>######Player: (1, 1)Stdout registrada completa
ARROWFALL######@.>######Player: (1, 1)O que este checkpoint estabelece
A imagem agora é uma visão do estado próprio. Construção, observação e percurso têm responsabilidades separadas, e a renderização não pode alterar Game por seu empréstimo const.
O estado ainda não muda depois da construção. O próximo checkpoint adiciona uma fronteira de entrada tipada e dá a Game a única operação de mutação da posição.
Exercício
Comece em (2, 1). Tanto @ quanto a coordenada impressa devem mudar. Depois restaure (1, 1).
Código completo e verificação
As seções abaixo preservam o código qualificado completo, o diff exato desde o predecessor e a sequência de comandos usada pela CI. São evidência de apoio para a implementação explicada acima.
Código completo testado (3 arquivos)
longbow.toml
[package]name = "arrowfall-step-02"version = "0.1.0"kind = "program"flecha = ">=0.3.0, <0.4.0"
[dependencies]longbow.lock
{ "schema": 1, "root": "root", "packages": [ { "id": "root", "identity": "local/arrowfall_step_02", "source": { "kind": "workspace", "path": "." }, "version": "0.1.0", "flecha": ">=0.3.0, <0.4.0", "dependencies": { "std": "toolchain-std" } }, { "id": "toolchain-std", "identity": "flecha/std", "source": { "kind": "toolchain" }, "dependencies": {} } ]}src/main.fl
module main;
structure Position { i32 x; i32 y;};
structure Game { private Position player;};
Position Position::at(i32 x, i32 y) { return build Position { self.x = x; self.y = y; };}
Game Game::start() { return build Game { self.player = Position::at(1, 1); };}
Position Game::playerPosition(&const self) { return self->player;}
void main() { state := Game::start(); player := state.playerPosition(); stdoutln("ARROWFALL"); stdoutln("#####"); draw_row(player); stdoutln("#####"); stdoutln("Player: (", player.x, ", ", player.y, ")");}
void draw_row(Position player) { stdout("#"); loop i32 x = 1; x < 4; x++ { if player.x == x and player.y == 1 { stdout("@"); } else if x == 3 { stdout(">"); } else { stdout("."); } } stdoutln("#");}Diff exato desde a versão anterior
diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/README.md tutorial/checkpoints/02-state/README.md--- tutorial/checkpoints/01-scene/README.md+++ tutorial/checkpoints/02-state/README.md@@ -1 +1 @@-# 01 — A room appears+# 02 — The picture becomes state@@ -5 +5 @@-There is no previous checkpoint: we begin with an empty Flecha program.+Checkpoint 01 prints a recognisable but hard-coded room.@@ -9,2 +9,2 @@-A terminal game first needs something recognisable on screen. Before state,-input, or rules, a static room gives the project a visible identity.+Changing a character in a string cannot express movement. The player needs a+position owned by game state, and drawing must read that position.@@ -14,2 +14,2 @@-Running the program prints Arrowfall's title and a tiny room containing the-player (`@`), floor (`.`), walls (`#`), and exit (`>`).+`Game` owns a `Position`; the scene derives `@` from it and reports `(1, 1)`.+The visible room is unchanged, but its source of truth is now data.@@ -28,2 +28,2 @@-The exact source diff is `../../diffs/01-scene.patch`. This directory contains-the complete current code, not a fragment.+See `../../diffs/02-state.patch` for the exact predecessor diff. All current+source is present in this directory.@@ -34,4 +34,4 @@-longbow check tutorial/checkpoints/01-scene-longbow test tutorial/checkpoints/01-scene-longbow build tutorial/checkpoints/01-scene-longbow run tutorial/checkpoints/01-scene < tutorial/checkpoints/01-scene/tests/input.txt+longbow check tutorial/checkpoints/02-state+longbow test tutorial/checkpoints/02-state+longbow build tutorial/checkpoints/02-state+longbow run tutorial/checkpoints/02-state < tutorial/checkpoints/02-state/tests/input.txt@@ -40,3 +39,0 @@-Expected stdout is `tests/output.txt`; the representative frame is-`tests/frame.txt` and `tests/frame.svg`.-@@ -45 +42,2 @@-1. **A room appears** — a static scene establishes the game's visual goal.+1. A room appears — static terminal scene.+2. **The picture becomes state** — structs and methods own the player position.@@ -49,2 +47,2 @@-Change one floor dot to a wall, rebuild, and verify exactly one character in-the frame changes. Restore it before moving on.+Set the start position to `(2, 1)`: both `@` and the reported coordinate must+move. Restore `(1, 1)` afterward.diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/longbow.lock tutorial/checkpoints/02-state/longbow.lock--- tutorial/checkpoints/01-scene/longbow.lock+++ tutorial/checkpoints/02-state/longbow.lock@@ -7 +7 @@- "identity": "local/arrowfall_step_01",+ "identity": "local/arrowfall_step_02",diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/longbow.toml tutorial/checkpoints/02-state/longbow.toml--- tutorial/checkpoints/01-scene/longbow.toml+++ tutorial/checkpoints/02-state/longbow.toml@@ -2 +2 @@-name = "arrowfall-step-01"+name = "arrowfall-step-02"diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/src/main.fl tutorial/checkpoints/02-state/src/main.fl--- tutorial/checkpoints/01-scene/src/main.fl+++ tutorial/checkpoints/02-state/src/main.fl@@ -2,0 +3,26 @@+structure Position {+ i32 x;+ i32 y;+};++structure Game {+ private Position player;+};++Position Position::at(i32 x, i32 y) {+ return build Position {+ self.x = x;+ self.y = y;+ };+}++Game Game::start() {+ return build Game {+ self.player = Position::at(1, 1);+ };+}++Position Game::playerPosition(&const self) {+ return self->player;+}+@@ -3,0 +30,2 @@+ state := Game::start();+ player := state.playerPosition();@@ -6 +34 @@- stdoutln("#@.>#");+ draw_row(player);@@ -7,0 +36,15 @@+ stdoutln("Player: (", player.x, ", ", player.y, ")");+}++void draw_row(Position player) {+ stdout("#");+ loop i32 x = 1; x < 4; x++ {+ if player.x == x and player.y == 1 {+ stdout("@");+ } else if x == 3 {+ stdout(">");+ } else {+ stdout(".");+ }+ }+ stdoutln("#");diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/tests/frame.svg tutorial/checkpoints/02-state/tests/frame.svg--- tutorial/checkpoints/01-scene/tests/frame.svg+++ tutorial/checkpoints/02-state/tests/frame.svg@@ -2,2 +2,2 @@-<svg xmlns="http://www.w3.org/2000/svg" width="360" height="124" viewBox="0 0 360 124" role="img">- <title>01-scene representative frame</title>+<svg xmlns="http://www.w3.org/2000/svg" width="360" height="143" viewBox="0 0 360 143" role="img">+ <title>02-state representative frame</title>@@ -9,0 +10 @@+ <text x="24" y="113" class="terminal">Player: (1, 1)</text>diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/tests/frame.txt tutorial/checkpoints/02-state/tests/frame.txt--- tutorial/checkpoints/01-scene/tests/frame.txt+++ tutorial/checkpoints/02-state/tests/frame.txt@@ -4,0 +5 @@+Player: (1, 1)diff -rN -U0 --exclude target tutorial/checkpoints/01-scene/tests/output.txt tutorial/checkpoints/02-state/tests/output.txt--- tutorial/checkpoints/01-scene/tests/output.txt+++ tutorial/checkpoints/02-state/tests/output.txt@@ -4,0 +5 @@+Player: (1, 1)Comandos de check, teste, build e execução
longbow check tutorial/checkpoints/02-statelongbow test tutorial/checkpoints/02-statelongbow build tutorial/checkpoints/02-statelongbow run tutorial/checkpoints/02-state < tutorial/checkpoints/02-state/tests/input.txtVersões deste tutorial
- 1. Renderize a primeira sala
- 2. Represente o estado do jogo
Próxima versão
A renderização agora lê o estado, mas nenhuma operação o altera. A próxima versão mapeia a entrada padrão para comandos de movimento.
Referências
Fonte: commit Arrowfall 56bb003, verificado com Flecha oficial 0.3.0. Código, entrada, saída, diff e frame desta página são lidos desse snapshot versionado.