Pular para o conteúdo

2. Represente o estado do jogo

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.

src/main.fl:3-28 (excerpt)
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.

src/main.fl:30-51 (excerpt)
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.

Comando de execução
longbow run tutorial/checkpoints/02-state < tutorial/checkpoints/02-state/tests/input.txt

O 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.

Frame de terminal produzido pela versão 2: Represente o estado do jogo
Frame representativo da execução registrada do checkpoint 2.

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
Transcript do frame
ARROWFALL
#####
#@.>#
#####
Player: (1, 1)
Stdout registrada completa
Stdout esperada
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

longbow.toml
[package]
name = "arrowfall-step-02"
version = "0.1.0"
kind = "program"
flecha = ">=0.3.0, <0.4.0"
[dependencies]

longbow.lock

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

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
Comandos de verificação
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

Versões deste tutorial

  1. 1. Renderize a primeira sala
  2. 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.

Leia 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.