ebSkola

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

SR 2.4.1. Problēmas dekompozīcija SR 2.4.7. Lietotāja saskarne SR 2.4.11. API SR 2.4.18. OOP SR 2.4.17. Datu krātuves SR 2.4.3. Programmatūras prototipēšana un testēšana

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.

  1. Izveido failu spele.py ar klasi class Spele:.
  2. Pievieno klases atribūtu ar visām 8 uzvaras kombinācijām.
  3. Uzraksti __init__ ar laukumu un pašreizējo spēlētāju.
  4. Uzraksti metodi gajiens(self, p), kas neļauj spēlēt aizņemtā rūtiņā.
  5. Uzraksti metodi uzvaretajs(self).
  6. 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ā.

  1. Izveido Flask maršrutu GET /api/spele, kas atgriež laukumu un gājiena kārtu.
  2. Izveido maršrutu POST /api/gajiens, kas pieņem rūtiņas numuru.
  3. Pārbaudi serverī, vai gājiens ir atļauts, un atgriez 400, ja nav.
  4. Atgriez pēc katra gājiena jauno spēles stāvokli.
  5. Izmēģini abus maršrutus ar curl.
  6. Mēģini spēlēt jau aizņemtā rūtiņā un pārbaudi atbildi.
  7. 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.

  1. Izveido index.html ar 9 pogām.
  2. Izsauc POST /api/gajiens, kad kāda poga nospiesta.
  3. Atjauno laukumu no atbildes pēc katra gājiena.
  4. Pievieno automātisku atjaunošanu ik pēc dažām sekundēm.
  5. Izvieto projektu Render pēc 12.5 stundas soļiem.
  6. Atver spēli divās ierīcēs un izspēlē vienu partiju.
  7. 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.

  1. Izveido tabulu partiju rezultātiem.
  2. Saglabā katras pabeigtās partijas uzvarētāju un datumu.
  3. Izveido maršrutu GET /api/statistika.
  4. Parādi lapā, cik reižu uzvarējis X un cik O.
  5. 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()}")
Uzvarētājs: X