Kółko i krzyżyk
1) Komponent planszy
W katalogu src/app zbudujmy komponent - planszę do gry w kółko i krzyżyk. Nazwijmy go xo.component. Zapisujemy go więc w pliku src/app/xo.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'xo-selector',
template : `<table className="plansza">
<tbody>
<tr>
<td x="0" y="0"> {{ xo[0] }} </td>
<td x="1" y="0"> {{ xo[1] }} </td>
<td x="2" y="0"> {{ xo[2] }} </td>
</tr>
<tr>
<td x="0" y="1"> {{ xo[3] }} </td>
<td x="1" y="1"> {{ xo[4] }} </td>
<td x="2" y="1"> {{ xo[5] }} </td>
</tr>
<tr>
<td x="0" y="2"> {{ xo[6] }} </td>
<td x="1" y="2"> {{ xo[7] }} </td>
<td x="2" y="2"> {{ xo[8] }} </td>
</tr>
<tr>
<td colSpan="3">{{ stanGry }} </td>
</tr>
</tbody>
</table>
`,
styleUrls: ['./xo.component.css']
})
export class XoComponent {
stanGry='KÓŁKO I KRYŻYK';
xo = [' ',' ',' ',' ',' ',' ',' ',' ',' '];
}
W pliku xo.component.css umieszczamy style dla naszej planszy. Mo szablonu w postaci własności template
.
Wstawiamy komponent do pliku app.component.html:
<xo-selector></xo-selector>
Ostatnią rzeczą jaką musimy zrobić, to wskazać Angularowi powiązanie między aplikacją na użytym selektorem. Zmieniamy w tym celu definicję modułu w pliku src/app/app.module.ts, dodając komponent XoComponent:
import { XoComponent } from './xo.component';
@NgModule({
declarations: [
AppComponent, XoComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent,XoComponent]
})
2) Użycie własności templateUrl wskazującej na zewnętrzny plik xo.template.html (dotychczasową wartość template przepisujemy do tego pliku):
templateUrl: './xo.component.html',
3) Obsługa zdarzeń
Zmieńmy nieco nasz komponent - tak, aby obsłużyć zdarzenie onclick (kliknięcie):
export class XoComponent {
stanGry: string ='KÓŁKO I KRYŻYK';
public xo: Array<string>;
constructor() {
this.xo = [' ',' ',' ',' ',' ',' ',' ',' ',' '];
}
public click(event, n, item) {
alert('Kliknąłem ['+n+']:' + item);
}
}
Po wywołaniu metody click - wyświetli się komunikat potwierdzający poprawne wywołanie.
Musimy dodać takie wywołanie do każdej komórki naszej planszy:
<tr>
<td x="0" y="0" (click)="click($event, 0, xo[0])"> {{ xo[0] }} </td>
<td x="1" y="0" (click)="click($event, 1, xo[1])"> {{ xo[1] }} </td>
<td x="2" y="0" (click)="click($event, 2, xo[2])"> {{ xo[2] }} </td>
</tr>
<tr>
<td x="0" y="1" (click)="click($event, 3, xo[3])"> {{ xo[3] }} </td>
<td x="1" y="1" (click)="click($event, 4, xo[4])"> {{ xo[4] }} </td>
<td x="2" y="1" (click)="click($event, 5, xo[5])"> {{ xo[5] }} </td>
</tr>
<tr>
<td x="0" y="2" (click)="click($event, 6, xo[6])"> {{ xo[6] }} </td>
<td x="1" y="2" (click)="click($event, 7, xo[7])"> {{ xo[7] }} </td>
<td x="2" y="2" (click)="click($event, 8, xo[8])"> {{ xo[8] }} </td>
</tr>
To powinno wystarczyć, aby kliknięcie w komórce planszy wywołało komunikat.
4) Logika - zmiana stanu planszy
Chcemy by stan planszy się zmieniał po kliknięciu. Nowa implementacja funkcji click():
public click(event, n, item) {
let newitem : string = ' ';
if (item==' ') newitem='x'
else if (item=='x') newitem='o';
this.xo[n]=newitem;
}
Stan komórki zmienia się wskutek kliknięć po kolei puste - x - o - puste.
5) Obsługa stanu gry
Wprowadźmy funkcję nowyStan (click ulega uproszczeniu). Potrzebujemy dodatkowo zmiennej w której zapamietamy czyj ruch (kto):
import { Component } from '@angular/core';
@Component({
selector: 'xo-selector',
templateUrl : 'xo.component.html',
styleUrls: ['./xo.component.css']
})
export class XoComponent {
stanGry='KÓŁKO I KRYŻYK';
xo = [' ',' ',' ',' ',' ',' ',' ',' ',' '];
kto = 'x'; // czyj ruch
private newState = (ix) => {
if (this.xo[ix] !== ' ') return;
this.xo[ix] = this.kto;
if (this.kto==='o') {
this.kto='x';
this.stanGry = 'ruch: x';
} else {
this.kto='o';
this.stanGry = 'ruch: o';
}
if ( ((this.xo[0] !== ' ') && (this.xo[0]===this.xo[1]) && (this.xo[0]===this.xo[2]))
|| ((this.xo[3] !== ' ') && (this.xo[3]===this.xo[4]) && (this.xo[3]===this.xo[5]))
|| ((this.xo[6] !== ' ') && (this.xo[6]===this.xo[7]) && (this.xo[6]===this.xo[8]))
|| ((this.xo[0] !== ' ') && (this.xo[0]===this.xo[3]) && (this.xo[0]===this.xo[6]))
|| ((this.xo[1] !== ' ') && (this.xo[1]===this.xo[4]) && (this.xo[1]===this.xo[7]))
|| ((this.xo[2] !== ' ') && (this.xo[2]===this.xo[5]) && (this.xo[2]===this.xo[8]))
|| ((this.xo[0] !== ' ') && (this.xo[0]===this.xo[4]) && (this.xo[0]===this.xo[8]))
|| ((this.xo[2] !== ' ') && (this.xo[2]===this.xo[4]) && (this.xo[2]===this.xo[6]))
) {
this.stanGry = 'KONIEC';
}
}
public click(event, n, item) {
this.newState(n);
}
}
Zobacz przykład [axo]
Powyższe rozwiązanie zmiany stanu nie jest zgodne z ideą Flux/Redux, według której nie powinno się zmieniać stanu fragmentami, ale za każdym razem wyliczać całościowo nowy stan komponentu. Każda taka zmiana powoduje rendering. Angular w tym miejscu dostarcza nieco magii - plansza się aktualizuje (bez jawnego wywołania renderingu) wskutek powiązań ze zmnienymi danymi.