12.6 Noslēguma projekts: Daudzspēlētāju Krustiņi un nullītes
Stundas uzdevums: Apvienot visu kursā apgūto, lai izveidotu pilnvērtīgu daudzspēlētāju "Krustiņi un nullītes" spēli, kas pieejama publiski internetā.
70 min plāns: Ievads un kritēriju pārruna (~10 min) · 1. uzdevums (~15 min) - uzraksti spēles klasi ar uzvaras pārbaudi · 2. uzdevums (~25 min) - pievieno API gājieniem un stāvoklim · 3. uzdevums (~20 min) - savieno pārlūku un izvieto mākonī. Papildu uzdevumu sāc tikai tad, ja pārējie trīs ir gatavi.
Pirms sāc: šis ir visa kursa noslēguma projekts. Tajā satiekas OOP, Flask, REST, JavaScript un izvietošana. Sāc ar spēles loģiku bez tīmekļa - to var pārbaudīt uzreiz.
Teorija: Spēles apraksts un arhitektūra
Tava noslēguma spēle ir daudzspēlētāju "Krustiņi un nullītes", kurā divi spēlētāji no atšķirīgiem datoriem var spēlēt vienu un to pašu partiju reālā laikā.
Tehnoloģijas (viss kurss vienā):
- Backend: Python + Flask + psycopg2 + PostgreSQL.
- Frontend: HTML + CSS + JavaScript (vanilla).
- OOP: klases
Spele,Speletajs,Gajiens. - API: REST ar JSON.
- Izvietošana: Render + PostgreSQL mākonī.
Galvenās funkcijas:
- Spēlētājs izveido jaunu spēli, saņem unikālu kodu (
ABC123). - Otrs spēlētājs ievada kodu un pievienojas spēlei.
- Spēles laukums un gājiens tiek glabāti datubāzē.
- Pārlūks polling-ē serveri ik 2 sek., lai redzētu pretinieka gājienus.
- Uzvarētāji tiek glabāti TOP 10 sarakstā.
Praktiskie uzdevumi
1. uzdevums -
Uzraksti spēles klasi ar uzvaras pārbaudi
Beigās spēles noteikumi strādās un būs pārbaudīti bez servera.
- Izveido failu
spele.pyar klasiclass Spele:. - Pievieno klases atribūtu ar visām 8 uzvaras kombinācijām.
- Uzraksti
__init__ar laukumu un pašreizējo spēlētāju. - Uzraksti metodi
gajiens(self, p), kas neļauj spēlēt aizņemtā rūtiņā. - Uzraksti metodi
uzvaretajs(self). - Pārbaudi klasi terminālī, izspēlējot vienu partiju bez pārlūka.
Gatavs, kad: terminālī var izspēlēt pilnu partiju, un uzvaru metode atrod pareizo uzvarētāju.
2. uzdevums -
Pievieno API gājieniem un stāvoklim
Beigās spēles stāvoklis dzīvos uz servera, nevis pārlūkā.
- Izveido Flask maršrutu
GET /api/spele, kas atgriež laukumu un gājiena kārtu. - Izveido maršrutu
POST /api/gajiens, kas pieņem rūtiņas numuru. - Pārbaudi serverī, vai gājiens ir atļauts, un atgriez 400, ja nav.
- Atgriez pēc katra gājiena jauno spēles stāvokli.
- Izmēģini abus maršrutus ar
curl. - Mēģini spēlēt jau aizņemtā rūtiņā un pārbaudi atbildi.
- Pieraksti, kāpēc uzvaras pārbaude ir serverī, nevis JavaScript.
Gatavs, kad: serveris noraida gājienu aizņemtā rūtiņā ar kodu 400 un atgriež pareizu stāvokli.
3. uzdevums -
Savieno pārlūku un izvieto mākonī
Beigās divi cilvēki varēs spēlēt no dažādiem datoriem.
- Izveido
index.htmlar 9 pogām. - Izsauc
POST /api/gajiens, kad kāda poga nospiesta. - Atjauno laukumu no atbildes pēc katra gājiena.
- Pievieno automātisku atjaunošanu ik pēc dažām sekundēm.
- Izvieto projektu Render pēc 12.5 stundas soļiem.
- Atver spēli divās ierīcēs un izspēlē vienu partiju.
- Veic commit, push un iesniedz skolotājam publisko saiti.
Gatavs, kad: divi spēlētāji no dažādām ierīcēm redz vienu un to pašu laukumu un var izspēlēt partiju.
Papildu uzdevums - Saglabā partijas datubāzē
Ja pamatdarbs ir gatavs, savieno projektu ar 10. tēmas datubāzi.
- Izveido tabulu partiju rezultātiem.
- Saglabā katras pabeigtās partijas uzvarētāju un datumu.
- Izveido maršrutu
GET /api/statistika. - Parādi lapā, cik reižu uzvarējis X un cik O.
- Pārbaudi, ka statistika saglabājas pēc servera restarta.
Gatavs, kad: pēc servera restarta statistika joprojām rāda visas iepriekšējās partijas.
Biežākās kļūdas
- Race condition: Divi spēlētāji vienlaikus izpilda gājienu - lieto datubāzes lock vai serializētu transakciju.
- Polling pārslodze: 2 sek. polling 100 spēlētāju = 50 pieprasījumi/sek. Apsver WebSocket nākotnē.
- Drošības robs: Klients var nosūtīt jebkuru pozīciju - vienmēr validē serverī, ka pozīcija ir tukša un ir attiecīgā spēlētāja gājiens.
Koda piemērs
class Spele:
"""Krustiņi un nullītes spēles loģika."""
UZVARES = [(0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6)]
def __init__(self, laukums="---------", pasreizejais="X"):
self.laukums = list(laukums)
self.pasreizejais = pasreizejais
def gajiens(self, poz, simbols):
if self.laukums[poz] != "-": raise ValueError("Aizņemta pozīcija")
if simbols != self.pasreizejais: raise ValueError("Nav tavs gājiens")
self.laukums[poz] = simbols
self.pasreizejais = "O" if simbols == "X" else "X"
def uzvaretajs(self):
for a, b, c in self.UZVARES:
if self.laukums[a] == self.laukums[b] == self.laukums[c] != "-":
return self.laukums[a]
if "-" not in self.laukums: return "neizšķirts"
return None
# Lietošana
s = Spele()
s.gajiens(0, "X"); s.gajiens(4, "O"); s.gajiens(1, "X"); s.gajiens(5, "O"); s.gajiens(2, "X")
print(f"Uzvarētājs: {s.uzvaretajs()}")