Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C, Limbo, Python: Ristinolla

Sivun loppuun

Macro [18.11.2009 15:44:52]

#

Päätin hylätä idean tehtä ääntä lähettävän viesti-ohjelman, ja tehdä pelin - ristinollan. Olen lukenut Tkinterin opasta, mutta jotkin asiat eivät käynneet ihan selviksi vielä. Kielenä edelleen Python.

1. Miten tehtiin kaksi eri osaa ohjelmaan, että toisessa voidaan pyörittää toista silmukkaa ja toisessa kysyä vaikka jotain? Näkyvätkö nämä silti yhtenä ikkunana?

2. Voiko Pythonilla piirtää/kirjoittaa tuohon ikkunaan helposti, että voisi toteuttaa ruudukon, X ja O?

3. Pelissä täytyy lukea hiiren sijainti paino hetkellä, jotta tiedetään mitä ruutua painetaan. Jos on 20x20px ruutuja, niin miten lasketaan minkä ruudun sisällä tämä on?

*---*---*---*
|   |   |   |
*---*---*---*
|   |   |   |
*---*---*---*
|   |   |   |
*---*---*---*

Jos ruudukko on tälläinen, niin millaisella laskulla tiedetään minkä ruudun päällä kursori on?

4. Voiton tarkistus. Miten näiden ruksien ja nollien sijainti kannattaa pitää tiedossa? Jos kirjattaisiin ne muuttujaan, niin tulisi tuossa 9 muuttujaa! Taulukossa päästäisiin vähemmällä, onko se järkevin tapa?

Teuro [18.11.2009 16:09:43]

#

1. Luultavasti ajattelet asiaa liian hankalasti. Riittää aivan takuulla, että suoritat ohjelmaa rivi kerrallaan yhdessä säikeessä.

2. pygamen avulla pitäisi onnistua haluttu ominaisuus.

3. Mikäli ruudut ovat olioita, niin luokkaan voidaan kirjoittaa vertailufunktio.

#include <iostream>
#include <vector>

class Ruutu{
	private:
		int x;
		int y;
		int leveys;
		int korkeus;
	public:
		Ruutu(int a, int b);
		bool onko_alueella(int x, int y);
};

Ruutu::Ruutu(int a, int b){
	this->x = a;
	this->y = b;
	this->leveys = 30;
	this->korkeus = 30;
}

bool Ruutu::onko_alueella(int x, int y){
	return (this->x < x && x < this->x + leveys && y > this->y && y < this->y + this->korkeus);
}

int main(){
	std::vector <Ruutu> Ruudut;

	Ruudut.push_back(Ruutu(30, 30));

	int tmp_x = 32;
	int tmp_y = 32;

	std::cout << Ruudut[0].onko_alueella(tmp_x, tmp_y) << std::endl;
	std::cout << Ruudut[0].onko_alueella(29, tmp_y) << std::endl;

    return EXIT_SUCCESS;
}

Sitten vain käyt jokaisella kierroksella ruudukon läpi ja katsot mitä tuo onko_alueella() metodi palauttaa, jos hiiren nappi on pohjassa.

Macro [18.11.2009 16:23:23]

#

En selittänyt tarpeeksi hyvin tuota ykköskohtaa. Tarvitsen siksi kolme säiettä, että yhteen tulee itse peli, toiseen tulee "ikuinen" silmukka joka hakee dataa ja kolmanteen tulee raw_input-kenttä. (Taisin puhua kahdesta tuolla aikaisemmin)

Kakkoskohta. Onko sillä helppo tulostaa asioita, että ei tarvitse kymmentä riviä koodia tulostaakseen tekstiä?

Kolmoskohta. Tämä on ehkä väärä kieli, kenties =) No, mutta kenties tuosta saa selvää.

Teuro [18.11.2009 16:30:01]

#

Luulen, että pärjäät kyllä yhdelläkin säikeellä. Tai jos välttämättä haluat pelata säikeillä, niin pidä huolta, että jokainen säie käisttelee dataa täysin itsenäisesti.

Pygamen sivuilta selviää aika hyvin tuon käyttö.

Metabolix [18.11.2009 16:33:07]

#

Mihin tarvitset raw_input-syötettä, jos kuitenkin käytät jotain parempaa kirjastoa? Myöskään datan hakemiseen ei tarvita erillistä säiettä, vaan voit vain tarkistaa toistuvasti, onko dataa tullut. Pääsilmukan rakenne on suunnilleen tällainen:

while True:
  if on_dataa:
    lue_data()
  if on_syotetta:
    lue_syote()
  if tilanne_muuttui:
    piirra_tilanne()
  nuku(hetki)

Suosittelen äskettäin julkaistun matopelioppaan lukemista. Vaikka kieli on eri, kaikki periaatteet pätevät aivan hyvin.

Antti Laaksonen [18.11.2009 16:57:39]

#

3. Voit laskea ruudun kaavalla, jonka osana on jakolasku ja pyöristys alaspäin. Mieti asiaa paperilla: miten saat selville ruudun, kun tiedät hiiren sijainnin?

4. Taulukon (tai Pythonissa listan) käyttäminen on hyvä ajatus.

jalski [18.11.2009 17:14:46]

#

Yksinkertaisimmillaan voit toteuttaa ristinollan peliruudukon Tk:n buttoneilla ja vaihtaa buttonin bitmappia tarvittaessa (esim. X, O tai tyhjä).

Toinen vaihtoehto on käyttää canvas widgettiä ja piirtää ruudukko ja pelimerkit itse canvaksen hyväksymillä komennoilla. Hiiren syötteen saat bindaamalla hiiren napit ja haluttu toiminto canvakseen.

Macro [18.11.2009 17:31:42]

#

jalski kirjoitti:

Yksinkertaisimmillaan voit toteuttaa ristinollan peliruudukon Tk:n buttoneilla ja vaihtaa buttonin bitmappia tarvittaessa (esim. X, O tai tyhjä).

Hei, hyvä ajatus! Näin sen voisi toteuttaa aluksi, niin se onnistuisi hyvin. =) Sitten siinä ei tarvitsisi laskea hiiren sijaintia. Mutta, buttonilla on sellaisen ärsyttävät reunat, kuten HTML:ssä buttonilla. Miten saan tämän vain isoksi (esim. 20x20px) alueeksi jota painettaessa tapahtuu jotain?

jalski [18.11.2009 17:51:21]

#

Kokeilepa pistää borderwidth 0 ja relief flat parametreiksi buttonille.

Macro [18.11.2009 18:24:30]

#

Kiitos, riitti kyllä pelkästään borderwidth =) Katsotaan, kun saan äkkiä demon valmiiksi.

Muokkaus. En saa millään nappeja niin, että olisi 3x3 "ruudukko" niistä. Ne menevät joko kaikki purkeen (vasen - oikea) tai pystysuoraan.
Entä, miten voin laittaa nappulan disaploiduksi?

jalski [18.11.2009 19:37:49]

#

Nappulan disablointi, aseta: -state disabled.

3x3 ruudukko: Laita vaikka kolmeen frameen kolmenappia vierekkäin kuhunkin ja pakkaa framet allekkain.

Macro [18.11.2009 19:53:45]

#

jalski kirjoitti:

3x3 ruudukko: Laita vaikka kolmeen frameen kolmenappia vierekkäin kuhunkin ja pakkaa framet allekkain.

Anteeksi?

Miten saisin Tk Label-tulostukset pyyhittyä pois jossain tietyssä vaiheessa? Kuten C++:ssa system("CLS").

jalski [18.11.2009 20:05:29]

#

Macro kirjoitti:

Miten saisin Tk Label-tulostukset pyyhittyä pois jossain tietyssä vaiheessa? Kuten C++:ssa system("CLS").

Pythonin Tk - toteutuksesta en ole varma, mutta Infernolle Limbolla ohjelmoidessa toimii labelille komento: configure -text { }

JP_94 [18.11.2009 20:27:32]

#

Tässä on toimiva esimerkkiohjelma 3x3-ruudukon luonnista Python 3:lla ja Tk:lla:

from tkinter import *

pohja = Tk()
pohja.title("3x3-ruudukko")

kehys = Frame(pohja)
kehys.pack()

kehys1 = Frame(kehys)
kehys1.pack()

kehys2 = Frame(kehys1)
kehys2.pack()

nappi1 = Button(kehys2, text="1",borderwidth=0,command="")
nappi1.pack(side=LEFT)
nappi2 = Button(kehys2, text="2",borderwidth=0,command="")
nappi2.pack(side=LEFT)
nappi3 = Button(kehys2, text="3",borderwidth=0,command="")
nappi3.pack(side=LEFT)

nappi4 = Button(kehys1, text="4",borderwidth=0,command="")
nappi4.pack(side=LEFT)
nappi5 = Button(kehys1, text="5",borderwidth=0,command="")
nappi5.pack(side=LEFT)
nappi6 = Button(kehys1, text="6",borderwidth=0,command="")
nappi6.pack(side=LEFT)

nappi7 = Button(kehys, text="7",borderwidth=0,command="")
nappi7.pack(side=LEFT)
nappi8 = Button(kehys, text="8",borderwidth=0,command="")
nappi8.pack(side=LEFT)
nappi9 = Button(kehys, text="9",borderwidth=0,command="")
nappi9.pack(side=LEFT)

pohja.mainloop()

Macro [19.11.2009 08:47:02]

#

Jep, kiitos teille. Nyt sain sen ihan oikein. Mutta yksi kysymys pyörii päässä.
Jos nappuloidan tekstit ovat listassa, ja jokaisella nappulalla text arvo on tekstit[monesko_nappula], niin mten saan tarkistettua onko tämä muuttunut viime kierroksesta? Jos laitan silmukkaan nuo nappuloiden tulostukset, niin siitähän tulee miljoona eri nappulaa.

vehkis91 [19.11.2009 09:17:17]

#

Tallennat edellisellä jokaisella kierroksella muistiin nappulan arvon ja seuraavalla kierroksella tarkistat, onko tämä arvo muuttunut.

Macro [19.11.2009 09:28:06]

#

Tuo on itsestään selvyys, mutta jos teen yhdellä kierroksella napin, ja seuraavalla kierroksella teen myös, niin niitähän tulee joka kierroksella yksi lisää jos jokin on muuttunut.

kierros += 1
UnboundLocalError: local variable 'kierros' referenced before assignment

Saan tälläisen virheen. Minulla on ohjelman alussa alustettu muuttuja kierros nollaksi, ja funktiossa tarkoitus olisi lisätä siihen yksi.
Nähtävästi tämä ei toimi, kun kierrosta ei ole määritelty funktion sisällä.

Muokkaus. Minulla näyttää koodi tällä hetkellä tältä:

# -*- coding: ISO-8859-1 -*-
from Tkinter import *

root = Tk()

def valikko(item):
    print "Painettiin", item

def helpp():
    Label(root, text="Ristinollapelin ohjeet...").pack()

kierros = 0
tekstit = ["Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhja"]

kierros = 1
def ristinolla(nappi):
    try:
        if kierros % 2 == 0:
            # Nollan vuoro
            tekstit[nappi] = "O"
        else:
            # Rastin vuoro
            tekstit[nappi] = "X"
    except:
        print "Virhe!"

    print nappi


menubar = Menu(root)

root.title("Ristinolla")
root.minsize(width=500, height=350)


# Päävalikko
filemenu = Menu(menubar)
filemenu.add_command(label="Uusi peli", command=valikko("Uusi peli"))
filemenu.add_command(label="Tallenna", command=valikko("Tallenna"))
filemenu.add_separator()
filemenu.add_command(label="Poistu", command=root.destroy)
menubar.add_cascade(label="MPS Ristinolla", menu=filemenu)

# Help-valikko
helpmenu = Menu(menubar)
helpmenu.add_command(label="Tietoa", command=helpp)
menubar.add_cascade(label="Apua", menu=helpmenu)

# Näytetään
root.config(menu=menubar)

# Luodaan rivit
kehys = Frame(root)
kehys.pack()
kehys1 = Frame(kehys)
kehys1.pack()
kehys2 = Frame(kehys1)
kehys2.pack()


nappi1 = Button(kehys2, text=tekstit[0], width=10, height=4, borderwidth=1, command=ristinolla(0))
nappi1.pack(side=LEFT)
nappi2 = Button(kehys2, text=tekstit[1], borderwidth=1, width=10, height=4, command=ristinolla(1))
nappi2.pack(side=LEFT)
nappi3 = Button(kehys2, text=tekstit[2], borderwidth=1, width=10, height=4, command=ristinolla(2))
nappi3.pack(side=LEFT)

nappi4 = Button(kehys1, text=tekstit[3], borderwidth=1, width=10, height=4, command=ristinolla(3))
nappi4.pack(side=LEFT)
nappi5 = Button(kehys1, text=tekstit[4], borderwidth=1, width=10, height=4, command=ristinolla(4))
nappi5.pack(side=LEFT)
nappi6 = Button(kehys1, text=tekstit[5], borderwidth=1, width=10, height=4, command=ristinolla(5))
nappi6.pack(side=LEFT)

nappi7 = Button(kehys, text=tekstit[6], borderwidth=1, width=10, height=4, command=ristinolla(6))
nappi7.pack(side=LEFT)
nappi8 = Button(kehys, text=tekstit[7], borderwidth=1, width=10, height=4, command=ristinolla(7))
nappi8.pack(side=LEFT)
nappi9 = Button(kehys, text=tekstit[8], borderwidth=1, width=10, height=4, command=ristinolla(8))
nappi9.pack(side=LEFT)
mainloop()

Aikas sekava, mutta koitan saada toimimaan. Ensiksi: Nyt kun funktioihin pitää antaa paramenteiksi jotain, niin nämä funktiot suoritetaan heti ohjelman alkuun. Nyt kun painaa valikosta jotakin, niin mitään ei tapahdu. Miten korjaisin niin, että kun painaa valikkosta jotain/nappia niin se suorittaisi funktion?

Teuro [19.11.2009 09:38:17]

#

Macro kirjoitti:

niin niitähän tulee joka kierroksella yksi lisää jos jokin on muuttunut.

Eihän tule, koska sinun pitää luoda kaikki tarvittavat napit tai ruudut ennen pääsilmukkaan menemistä. Virheen pitäisi korjaantua, jos laitat muuttujan 'kierros' globaaliksi.

Macro [19.11.2009 09:44:10]

#

Käsitin vähän eri tavalla tämän logiikan, tietenkin ne määritellään siellä. =)

Ei auttanut vaikka asetin kerros-muuttujan globaaliksi.

jalski [19.11.2009 10:41:30]

#

Selkeyttäisin vähän käyttöliittymän määrittelyä. Tämä kannattaa miettiä huolella loogiseksi ja helpoksi hahmottaa.

Nyt ensimmäisen kehyksen parent on root (toplevel ikkuna), toisen on kehys1 ja kolmannen on kehys2. Jos meinaat myöhemmin lisätä tavaraa käyttöliittymään, niin loogisempi olisi märitellä:

- napit frame, jolle parent root
- napitrivi1 frame, jolle parent napit
- napitrivi2 frame, jolle parent on napit
- napitrivi3 frame, jolle parent on napit

- Määrittele ja pakkaa kolme buttonia jokaiseen napitrivi kehykseen.
- pakkaa napitrivi kehykset napit kehykseen.

Macro [19.11.2009 14:41:40]

#

For-silmukkalla varmaan saisi toteutettua helpommin nämä nappulat. PHP:llä osaisinkin, mutta miten saan tulostettua Pythonilla niitä nappuloita? Osaan kyllä for silmukat sun muut, mutta kun niitä frameja pitää luoda, niin menee vaikeammaksi.

Esimerkki: Käyttäjä antaa luvun 7. For silmukassa tulostetaan 7x7 määrä nappeja.

Metabolix [19.11.2009 14:50:11]

#

n = 7

# Luodaan listat.
kehykset = [None] * n
napit = [None] * n

# Luodaan kehykset ja napit. Lisää loput parametrit ja pack-kutsut!
for i in range(0, 7):
    kehykset[i] = Frame(root)
    napit[i] = [None] * n
    for j in range(0, 7):
        napit[i][j] = Button(kehykset[i])

Tuo command-juttu ei koodissasi toimi aivan oikein. Funktiokutsu suoritetaan napin luomisen yhteydessä ja command-parametriksi tulee funktion paluuarvo (tässä None, koska funktio ei palauta mitään). Voit lukea ongelman ja ratkaisun täältä (löytyi listan kärjestä hakusanalla Tkinter button command).

Macro [19.11.2009 15:05:18]

#

En saanut koodiasi toimimaan halutulla tavalla. Lisäsin parametrit ja packit, mutta ei silti lähtenyt toimimaan.

napit[i][j] = Button(kehykset[i], command=nappula).pack(side=LEFT)

Tulostuksen koitin hoitaa print napit[numero][numero], mutta ei toiminut. (Lisäsin myös kaikki import-kohdat sun muut) Pitikö se noin tehdä?

Kiitos tuosta linkistä, se oli hyvä neuvo =)

Metabolix [19.11.2009 15:20:36]

#

Et voi laittaa sitä packia tuohon, vaan se pitää laittaa seuraavalle riville. Nythän sijoitat sen paluuarvon napit-listaan, ja paluuarvo on sattumoisin None eikä mikään nappi. (Aiemmassa koodissasi olet tehnyt sen ihan oikein, joten miksi rikoit sen nyt?)

Macro [19.11.2009 15:27:42]

#

Ehken ole vielä välttämättä valmis tähän Tk:n harjoitteluun, kun ei näytä millään onnistuvan. Tai, pitäisikö lukea enemmän tai lainata jokin kirja kirjastosta?

Koitin laittaa sen eritavalla, mutta siltikään ei tulostu mitään.

Macro [19.11.2009 17:08:22]

#

En ymmärrä, että miten laitan sen toisen rivin tuohon, koska buttonilla ei ole nimeä. Miten se pitää tehdä? Nyt on kyllä ideat vähissä.

Metabolix [19.11.2009 17:10:26]

#

Koodista saa aivan toimivan, kun lisää framelle ja buttonille pack-rivit heti muodostimien jälkeen.

Macro [19.11.2009 17:11:41]

#

Se tässä onkin ongelmana...

Metabolix [19.11.2009 17:19:27]

#

Sovelsin koodin tässä keskustelussa esitellyistä pätkistä:

from Tkinter import *
root = Tk()

n = 7

# Luodaan listat.
kehykset = [None] * n
napit = [None] * n

# Luodaan kehykset ja napit.
for i in range(0, 7):
    kehykset[i] = Frame(root)
    kehykset[i].pack()
    napit[i] = [None] * n
    for j in range(0, 7):
        napit[i][j] = Button(kehykset[i], text = "%d,%d" % (i, j))
        napit[i][j].pack(side = LEFT)

Ehkä se helpompien asioiden tekeminen alkuun on ihan viisas ajatus... :)

Macro [19.11.2009 17:32:26]

#

Yksinkertaisia asioita on hankala keksiä =)

Sellainen juttu vielä, että jos a:n arvo on ennen silmukoita 0, ja joka kierroksella pitäisi lisätä siihen yksi (a += 1), niin miten se onnistuu? Kun laitan commandiksi funktio(a), niin a on aina n * n... (Helppoja asioita on hankala keksiä)

Metabolix [19.11.2009 17:45:22]

#

def f(i, j):
    print i, j

def g(i, j):
    return lambda: f(i, j)

a = g(1, 2)
b = g(3, 4)
a() # lambda: f(1, 2) => f(1, 2) => print 1, 2
b() # lambda: f(3, 4) => f(3, 4) => print 3, 4

jalski [19.11.2009 19:20:14]

#

Mihin muuten oikeastaan tarvitset silmukoita event-pohjaisessa pelissä, kuten ristinolla?

Jos haluat, että joka toisella painalluksella "ruutuun" tulee "X" ja "O", niin mikset ratkaise tätä vain muutamalla apumuuttujalla:

#En osaa pythonia, mutta jotenkin näin...

current = "X"
last = "O"

# tilan vaihto

temp = current
current = last
last = temp

Metabolix [19.11.2009 19:24:06]

#

Missä näet silmukoita? Minusta tässä on keskusteltu nyt vain nappien luomisesta, ja viimeisimmässä koodissa on käytetty kierroksen jakojäännöstä siirtäjän selvittämiseen – aivan oikein ja järkevästi.

jalski kirjoitti:

# tilan vaihto

temp = current
current = last
last = temp

Pythonissa tyylikkäämmin näin:

current, last = last, current

Macro [19.11.2009 19:25:12]

#

Jäinköhän jostain paitsi? En nyt ihan käsittänyt, että mitä tarkoitat tällä. Nyt tällä tyylillä saan kaikkiin arvon None/(14, 14).

Siis, joka nappulan kohdalla lisättäisiin a-muuttujaan 1. Yhdessä for-silmukassa se olisi helppo, kutta kun niitä on kaksi niin tulee vähän mitä tulee.

Metabolix [19.11.2009 19:29:13]

#

Macro kirjoitti:

Siis, joka nappulan kohdalla lisättäisiin a-muuttujaan 1. Yhdessä for-silmukassa se olisi helppo, kutta kun niitä on kaksi niin tulee vähän mitä tulee.

Miten niin? Lisäät vain siellä sisimmässä silmukassa samalla, kun luodaan uusi nappulakin.

Käsitin, että ongelmasi liittyi tuohon command-asiaan. Jos käytät suoraan lambdaa uuden funktion luomiseen, siinä oleva a-muuttuja viittaa globaaliin a:han, joka jatkaa kasvuaan. Esimerkissäni luodaan funktio toisessa funktiossa, jotta siihen saadaan oikeat (muuttumattomat) arvot.

Macro [19.11.2009 19:42:55]

#

Se kyllä palauttaa aina yhden muuttumattoman arvon, mutta se ei kyllä ole oikea. Jokaisella nappulalla on nyt arvo (14, 14).

# -*- coding: iso-8859-1 -*-
from Tkinter import *
import tkSimpleDialog
import tkMessageBox

root = Tk()

X = 0
O = 0

#Aluksi koko on 3 * 3
koko = 3

root.title("Ristinolla")
root.iconbitmap('favicon.ico')

def tallenna():
    print "Tallennus"
    #...

def uusi_peli():
    if tkMessageBox.askyesno("Uusi peli?", "Haluatko varmasti aloittaa uuden pelin?"):
        uusi_peli = tkSimpleDialog.askstring("Uusi peli", "Anna nimi uudelle pelille:")

        if len(uusi_peli) <= 0:
            uusi_peli = "Uusi peli"

        X = 0
        O = 0

        tekstit = ["Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä", "Tyhjä"]
        root.title(uusi_peli)

        peliruutu = tkSimpleDialog.askstring("Koko", "Peliruudun koko:")
        koko = peliruutu
        #Debuggaus
        print "peliruutu", peliruutu
        print "koko", koko

#Debuggaus
print "koko:", koko

def ristinolla(palikka):
    print "Painettiin nappulaa", palikka

menubar = Menu(root)

filemenu = Menu(menubar)
filemenu.add_command(label="Uusi peli", command=uusi_peli)
filemenu.add_command(label="Tallenna", command=tallenna)
filemenu.add_separator()
filemenu.add_command(label="Poistu", command=root.destroy)
menubar.add_cascade(label="Ristinolla", menu=filemenu)

# Näytetään
root.config(menu=menubar)

#Debuggaus
print "koko: ", koko
# Luodaan listat: n kehystä, n*n nappia.
kehykset = [None] * koko
napit = [[None] * koko] * koko

# Luodaan kehykset ja napit.
for i in range(0, koko):
    kehykset[i] = Frame(root)
    kehykset[i].pack()
    for j in range(0, koko):
        napit[i][j] = Button(kehykset[i], text="Tyhjä", command=lambda: ristinolla(a))
        napit[i][j].pack(side=LEFT)

mainloop()

En oikein ymmärrä tätä tilannetta. Jos avataan uusi peli, niin siinä voidaan myös määrittää laudan koko. Aluksi funktiossa nämä tulostuvat ihan oikein (Sama mitä syötetty), mutta tuon for silmukan edessä on ongelma. koko-muuttuja on taas 3, ja tulee pieni taulukko.

#Debuggaus
print "koko:", koko

Kun on aloitettu uusi peli valikosta, niin ohjelma jää tähän, kunnes painetaan nappia. Miksei se jatka suoritusta koodin loppuun asti? Miten saan ohjelman toimimaan niin, että se tietää oikean pelilaudan koon käyttäjän syötteestä?

Muokkaus. Ehditkin muuttaa lähettämääsi nappientulostus-silmukkaa. Sainkin sen tuolla toimimaan, mutta nyt on tämä koko asia enään.

Metabolix [19.11.2009 19:45:31]

#

Ja katsopa sitten uudestaan sitä esimerkkiäni. Juuri tuon takia siinä on erillinen funktio, joka palauttaa lambdafunktion. Sinun versiossasi a viittaa globaaliin a-muuttujaan, minun esimerkissäni taas saadaan lambdafunktion i:ksi ja j:ksi g:lle annettujen parametrien arvot kutsuhetkellä.

Ja nyt siis puhun tuosta lambdafunktioesimerkistä enkä mistään nappuloista.

Macro [19.11.2009 19:46:17]

#

Taisitkin muokata sitä esimerkkiä sen jälkeen, kun kopioin sen. Nyt ne arvot toimivat, mutta on toinenkin ongelma (Edellinen viesti).

Muokkaus. Ei nyt vitsi... Tuo lambda on kyllä kummallinen tapaus. Voitko korjata tuon esimerkkini for-silmukat niin, että vaikka siellä olisi se lambda niin nappien toiminto olisi oikein. Siis niin kuin selitit noilla parilla funktiolla, mitä en ymmärtänyt.

Tarkoitus olisi, että napilla 0 on tehtävä lambda: funktio(0), napilla 1 tehtävä lambda: funktio(1) jne...

jalski [19.11.2009 19:46:38]

#

Metabolix kirjoitti:

Missä näet silmukoita? Minusta tässä on keskusteltu nyt vain nappien luomisesta, ja viimeisimmässä koodissa on käytetty kierroksen jakojäännöstä siirtäjän selvittämiseen – aivan oikein ja järkevästi.

Pythonissa tyylikkäämmin näin:

current, last = last, current

Viittasin ylempään koodiin, missä on osasena:

def ristinolla(nappi):
    try:
        if kierros % 2 == 0:
            # Nollan vuoro
            tekstit[nappi] = "O"
        else:
            # Rastin vuoro
            tekstit[nappi] = "X"
    except:
        print "Virhe!"

    print nappi

Ai Pythonissa on tuple -tuki, Limbolla vastaava olisi:

(current, last) = (last, current);

Macro [20.11.2009 13:54:35]

#

Juu... Nytten olisi vielä se nappulan numerointiongelma (1, 2, 3, 4 jne...) ja kierros-muuttuja. Ohjelman alussa muuttuja kierros asetetaan nollaksi, ja siihen pitäisi lisätä aina 1 funktiossa ristinolla(). Saan kuitenkin virheen

UnboundLocalError: local variable 'kierros' referenced before assignment

vehkis91 [20.11.2009 16:02:18]

#

sun pitää välittää parametrina se kierros tolle funktiolle. Ja jos tarviit vielä kutsujassa sen keirros muuttujan arvoa, sun pitää vielä laittaa funktio palauttamaan se.

Macro [20.11.2009 16:34:18]

#

Tein tälläisen systeemin testiksi:

def funktio(nappula, kierrokset):
    print "Nappula", nappula

    kierros = kierrokset + 1
    print "Kierros:", kierros, "\n"

while True:
    nappi = raw_input("Nappula: ")
    funktio(nappi, kierros)

Tämän mukaan kierros on joka kierroksella 1. Kun funktiossa lisätään kierros-muuttujaan joka kerralla kierros-muuttujan arvo + 1, niin miksei se kasva joka kerta, vaan on aina aluksi 0? Mitä pitää tehdä?

trilog [20.11.2009 16:51:06]

#

Kyseessä on muuttujien näkyvyysalueet. Kierros-muuttuja on näkyvillä vain funktion funktio sisällä, eikä sen muutokset välity ulkopuolelle. Tästä päästäänkin erääseen Pythonin erikoisuuteen; pääperiaate on, että kaikki muuttujat välitetään funktioon viittauksena, paitsi numero- ja tekstityypit:

http://www.penzilla.net/tutorials/python/functions/:

Python passes all arguments using "pass by reference". However, numerical values and Strings are all immutable in place. You cannot change the value of a passed in variable and see that value change in the caller. Dictionaries and Lists on the other hand are mutable, and changes made to them by a called function will be preserved when the function returns. This behavior is confusing and can lead to common mistakes where lists are accidentally modified when they shouldn't be. However, there are many reasons for this behavior, such as saving memory when dealing with large sets.

Ratkaisuna on käyttää esimerkiksi lista-tietotyyppiä tai määritellä muuttuja globaaliksi.

Macro [20.11.2009 16:56:09]

#

Miten muuttuja määritetään globaaliksi? Laitoin ennen funktiota global kierros, mutta ei vaikuta yhtään mitään.

jalski [20.11.2009 16:57:05]

#

Eiköhän tuossa ristinolla pelissä nyt kuitenkin riitä peliruudukon napeille yksi funktio, mikä käsittelee painalluksen ja ottaa parametrikseen napin sijainnin ruudukossa.

Eikös lambda ole vain "nimetön" funktio ja joku allaolevan tapainen riitä linkitsemään toiminteen napille, suoritettuna nappien luonti silmukassa? :

napit[i][j] = Button(kehykset[i], text="Tyhjä", command=lambda: ristinolla(i, j))

Macro [20.11.2009 17:08:48]

#

Kiitos hienojen hakusanojen (Python global argument), sain toimimaan tämän koodini. Katsotaan kun jään seuraavaan vaiheeseen jumiin...

def funktio(nappula, kierrokset):
    global kierros
    print "Nappula", nappula

    kierros = kierrokset + 1
    print "Kierros:", kierros, "\n"

while True:
    nappi = raw_input("Nappula: ")
    funktio(nappi, kierros)

Metabolix [20.11.2009 17:11:36]

#

jalski: Tuossa nimettömään funktioon eivät tule i:n ja j:n arvot vaan viittaukset muuttujiin, joten jokainen nappi saa täsmälleen saman toiminnon. Nimetön funktio pitää luoda toisella funktiolla. Kirjoitin tuosta oikein esimerkin pari viestiä sitten, mutta se taisi olla ilman selityksiä liian vaikea hahmottaa.

jalski [20.11.2009 18:06:53]

#

Metabolix kirjoitti:

jalski: Tuossa nimettömään funktioon eivät tule i:n ja j:n arvot vaan viittaukset muuttujiin, joten jokainen nappi saa täsmälleen saman toiminnon. Nimetön funktio pitää luoda toisella funktiolla. Kirjoitin tuosta oikein esimerkin pari viestiä sitten, mutta se taisi olla ilman selityksiä liian vaikea hahmottaa.

Nopeasti dokumentaatiota lukaisten käsitin hiukan toisin. Mielestäni, jos command määrittelylle antaa pelkän callback-funktion nimen ilman sulkeita tai parametrejä, niin Python ei suorita funktiota ja sijoita funktion paluuarvoa callback-funktioksi vaan määrittää sen suoraan.

Eikös allaoleva määrittele vain nimettömän funktion, mikä kutsuu oikeata callback-funktiota oikeilla parametreillä:

lambda: ristinolla(i, j)

Metabolix [20.11.2009 18:13:50]

#

Ei.

def f(x):
  print x

arvo = 1
a = lambda: f(arvo)
arvo = 2
b = lambda: f(arvo)
arvo = 3

a() # 3
b() # 3

Tuon takia pitää palauttaa se funktio toisesta funktiosta, kuten aiemmin sanoin. Silloin siihen sidotaan funktion (väliaikaiset) parametrit eikä globaaleja muuttujia.

jalski [20.11.2009 19:18:21]

#

Auts, mutta kokeilepa allaolevaa:

def f(x):
  print x

arvo = 1
a = lambda arvo = arvo: f(arvo)
arvo = 2
b = lambda arvo = arvo: f(arvo)
arvo = 3

a() # 1
b() # 2

Metabolix [20.11.2009 19:32:35]

#

jalski kirjoitti:

napit[i][j] = Button(kehykset[i], text="Tyhjä", command=lambda: ristinolla(i, j))

Ei, ei tuo edelleenkään toimi.

Omankin esimerkkisi perusteella toimiva versio menisi näin:

napit[i][j] = Button(kehykset[i], text="Tyhjä", command=lambda i=i, j=j: ristinolla(i, j))

Minusta ei kuitenkaan ole kovin tyylikästä käyttää oletusargumentteja tuohon, vaan siistimpi ratkaisu olisi tämä:

def ristinolla_cb(i, j):
  return lambda: ristinolla(i, j)

napit[i][j] = Button(kehykset[i], text = "Tyhjä", command = ristinolla_cb(i, j))

(Edit: jalski näköjään muokkasi jo viestiään. Niin minäkin. :))

jalski [20.11.2009 19:37:52]

#

Metabolix kirjoitti:

Ei, ei tuo edelleenkään toimi. (Mikset kokeile ennen ehdottelua? Edit: Muokkasitkin näköjään viestiäsi.)

Copy-pastasin ylhäältä ja unohdin muokata vahingossa... Otin sitten pois, kun ajattelin, että tuosta näkee muutenkin mitä pitäisi muokata.

jalski [21.11.2009 12:22:27]

#

Täytyy kyllä sanoa, että mielestäni Infernon Tk-toteutuksen tapa käyttää kanavia ja merkkijonoja on Python toteutusta joustavampi ja helpompi käyttää.

Alla esimerkkinä Limbo -ohjelma, mikä piirtää nappiruudukon, laittaa napin painalluksella X:n tai O:n vuoronperään ruudukkoon ja kirjoittaa stardard outputtiin painetun napin koordinaatit ruudukossa:

implement Testi;

include "sys.m";
   sys: Sys;
   Dir: import sys;

include "draw.m";
   draw: Draw;
   Screen, Display, Image, Context, Point, Rect: import draw;

include "tk.m";
   tk: Tk;
   Toplevel: import tk;

include "tkclient.m";
   tkclient: Tkclient;
   Hide: import tkclient;


Testi : module
{
   init: fn(ctxt: ref Draw->Context, argv: list of string);
};


WinBut : con Hide;
BoardSize: con 10;

ctxt: ref Draw->Context;
main : ref Tk->Toplevel;



init(xctxt: ref Draw->Context, nil: list of string)
{
   sys  = load Sys  Sys->PATH;

   if (xctxt == nil) {
      sys->fprint(sys->fildes(2), "testi: no window context\n");
      raise "fail:bad context";
   }

   ctxt = xctxt;

   draw = load Draw Draw->PATH;
   tk   = load Tk   Tk->PATH;
   tkclient= load Tkclient Tkclient->PATH;

   sys->pctl(Sys->NEWPGRP, nil);

   tkclient->init();

   (t, wmctl) := tkclient->toplevel(ctxt, nil, "Testi", WinBut);
   if(t == nil)
   {
      sys->fprint(sys->fildes(2), "testi: creation of toplevel window failed\n");
      raise "fail:creation of toplevel window failed";
   }

   main = t;

   cmd := chan of string;
   tk->namechan(main, cmd, "cmd");

   display_board();

   (current, last) := ("X", "O");

   center(main);
   tkclient->onscreen(main, "exact");
   tkclient->startinput(main, "kbd"::"ptr"::nil);

   for(;;) alt {
      s := <-main.ctxt.kbd =>
         tk->keyboard(main, s);

      s := <-main.ctxt.ptr =>
         tk->pointer(main, *s);

      s := <-wmctl or
      s = <-main.ctxt.ctl or
      s = <-main.wreq =>
         tkclient->wmctl(main, s);

      # täällä käsitellään tiedot pelilaudan painalluksista
      bcmd := <-cmd =>
         (nil, tokens) := sys->tokenize(bcmd, " ");
         case hd tokens {
            "b" =>
               x := int hd tl tokens;
               y := int hd tl tl tokens;

               sys ->print("Board( %d, %d ) pressed\n", x, y);

               tk->cmd(main, sys->sprint(".f%d.b%d configure -text {%s}", x, y, current));
               tk->cmd(main, sys->sprint(".f%d.b%d configure -state disabled", x, y));
               tk->cmd(main, "update");

               (current, last) = (last, current);
         }

   }

}



center(t: ref Tk->Toplevel)
{
   org: Point;
   ir := tk->rect(t, ".", Tk->Border|Tk->Required);
   org.x = t.screenr.dx() / 2 - ir.dx() / 2;
   org.y = t.screenr.dy() / 2 - ir.dy() / 2;

   if (org.y < 0)
      org.y = 0;

   tk->cmd(t, ". configure -x " + string org.x + " -y " + string org.y);
}



display_board()
{
   i, j: int;
   pack: string;

   tk->cmd(main, "frame .f");
   tk->cmd(main, "pack .f -fill x");

   for (i = 1; i <= BoardSize; i++) {
      tk->cmd(main,  sys->sprint("frame .f%d", i));
      pack = "";
      for (j = 1; j <= BoardSize; j++) {
         pack += sys->sprint(" .f%d.b%d", i, j);
         tk->cmd(main, sys->sprint("button .f%d.b%d -text { } -width 14 -command {send cmd b %d %d}", i, j, i, j));
         }

      tk->cmd(main, sys->sprint("pack %s -side left", pack));
      tk->cmd(main, sys->sprint("pack .f%d -side top -fill x", i));
   }

   tk->cmd(main, "update");

}

Metabolix [21.11.2009 14:08:09]

#

Mielipiteesi ehkä johtuu siitä, että osaat Limboa paljon paremmin kuin Pythonia. Minusta tästä taas tulee Pythonilla paljon selkeämpi. (Ohjelma ei ole täsmälleen sama, mutta kyllä siinä on nappeja ja ristinolla tarkistuksineen.)

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import Tkinter

def kaynnista():
	global kierros, napit, ruudut, ruutuja
	ikkuna = Tkinter.Tk()
	kierros = 0
	napit = {}
	ruudut = {}
	for y in range(0, ruutuja):
		kehys = Tkinter.Frame(ikkuna)
		kehys.pack()
		for x in range(0, ruutuja):
			napit[(x, y)] = Tkinter.Button(kehys, command = painettu_cb(x, y), width = 1)
			napit[(x, y)].pack(side = Tkinter.LEFT)
	Tkinter.mainloop()

def painettu_cb(x, y):
	return lambda: painettu(x, y)

def painettu(x, y):
	global kierros, napit, ruudut
	ruudut[(x, y)] = 'XO'[kierros % 2]
	print("Ruutu (%d, %d) on nyt %c." % (x, y, ruudut[(x, y)]))
	napit[(x, y)].configure(text = ruudut[(x, y)], command = False)
	kierros = kierros + 1
	if tarkista_voitto(x, y, ruudut[(x, y)]):
		for nappi in napit.itervalues():
			nappi.configure(command = False)
		print("Peli loppui, voittaja on %c." % (ruudut[(x, y)]))

def tarkista_voitto(x, y, m):
	global voittopituus
	# Ruutu (x, y) lasketaan kahdesti, joten vertailuna > eikä >=.
	if (pituus(x, y, 0, -1, m) + pituus(x, y, 0, 1, m) > voittopituus):
		return True
	if (pituus(x, y, -1, -1, m) + pituus(x, y, 1, 1, m) > voittopituus):
		return True
	if (pituus(x, y, -1, 0, m) + pituus(x, y, 1, 0, m) > voittopituus):
		return True
	if (pituus(x, y, 1, -1, m) + pituus(x, y, -1, 1, m) > voittopituus):
		return True
	return False

def pituus(x, y, dx, dy, merkki):
	global ruudut
	lkm = 0
	while ruudut.has_key((x, y)) and ruudut[(x, y)] == merkki:
		x, y, lkm = x + dx, y + dy, lkm + 1
	return lkm

ruutuja, voittopituus = 15, 5
kaynnista()

(Mahtavaa, ensimmäinen millään mittapuulla kunnollinen Tk-ohjelmani.)
Edit. Lyhensin koodia vielä vähän.

Macro [21.11.2009 14:19:21]

#

Hyvä Metabolix, tuon kannalta saan helpommin tehtyä oman pelini. Hienoa, että täällä on näin taitavaa porukkaa. Jos osaisin yhtä hyvin ohjelmoida, niin olisin hyvin onnellinen. Toivottavasti sinä olet. =)

Chiman [21.11.2009 15:14:02]

#

Hyöty on tässä ketjussa ehkä hieman kyseenalainen, mutta Pythonin esittelyn kannalta näytän toisenkin toteutustavan Metabolixin tarkista_voitto-funktiolle.

def tarkista_voitto(x, y, m):
    suunnat = ((0, 1), (1, 1), (1, 0), (-1, 1))

    def merkkeja_voittoon(sx, sy):
        # Ruutu (x, y) lasketaan kahdesti, joten vertailuna > eikä >=.
        return pituus(x, y, sx, sy, m) + pituus(x, y, -sx, -sy, m) > voittopituus

    return any(merkkeja_voittoon(sx, sy) for sx, sy in suunnat)

Erotin alkuperäisistä if-lauseista poikkeavat kohdat tupleksi (suunnat). tarkista_voitto-funktio palauttaa True, jos jossakin (any) suunnassa on tarpeeksi merkkejä voittoon, muuten False. Irrotin selkeyden vuoksi tarkemmat yksityiskohdat paikalliseen funktioon. Koska voittopituutta ei muuteta funktiossa, global-mainintaa ei tarvita. Globaalia arvoa käytetään automaattisesti, koska samalla nimellä ei ole lokaalia muuttujaa.

Any-funktion argumenttina oleva "...for...in..." -rakennelma on nimeltään generaattorilauseke.

Pahoittelen jos sotken itse asiaa :) Tätä ei tarvitse ymmärtää ristinollapeliä varten.

jalski [21.11.2009 15:23:37]

#

Infernon kanavatoteutuksen joustavuudella tarkoitin sitä, että ikkunatoimintojen käsittely silmukka voi toimia omassa säikeessään, pelitapahtumien käsittely silmukka omassaan (=pelin päälooppi) ja mahdollisen toisen pelaajan komennot verkon yli saa välitettyä pelille käytännössä ilman muutoksia pelitapahtumien käsittely logiikkaan. Kaikki tämä toimii synkronoituna kanavien avulla.

Macro:
Tästä ristinollasta tulisi hyvä kahden pelaajan client-server peli. On todennäköisesti vielä helpompi toteuttaa, kuin kehittää kakkospelaajalle hyvin toimiva tekoäly (=random-älyä ei lasketa hyväksi). Metabolixin hyvän Tk-esimerkin pohjalta pääset varmasti eteenpäin.

Macro [21.11.2009 15:46:58]

#

jalski: Tämä oli alunperin tarkoituskin, tehdä siis kahden pelattava "nettipeli". Ajattelin vain, että kun en ole mitään peliä koskaan tehnyt, niin on helpompi aloittaa sen tekeminen varovasti. Client-server tyylillä sitä ei ole välttämättä hankala toteuttaa, mutta miten tästä tehtäisiin client-client, että ei tarvitse pelistä kahta eri versiota?

Muokkaus. Miten voisin tyhjentää kaikki framet ja siirtyä koodin alkuun? Niinkuin käynnistäisi ohjelman uudelleen.

jalski [21.11.2009 16:25:52]

#

Macro kirjoitti:

Client-server tyylillä sitä ei ole välttämättä hankala toteuttaa, mutta miten tästä tehtäisiin client-client, että ei tarvitse pelistä kahta eri versiota?

Voit sisällyttää ohjelmaan molemmat sekä serverin, että asiakkaan ja ottaa komentoriviltä parametriksi toimitaanko serverinä vai asiakkaana. Sitten vain toimit valinnan mukaan. Tämä onnistunee muutamalla ehtolauseella.

Toinen vaihtoehto on erillisen serverin tekeminen, mikä palvelee kahta asiakasta ja toimii välittäjänä asiakkaiden välillä.

Macro kirjoitti:

Miten voisin tyhjentää kaikki framet ja siirtyä koodin alkuun? Niinkuin käynnistäisi ohjelman uudelleen.

Ei kai niitä kaikkia kehyksiä tyhjentää tarvi? Riittää, kun määrität kaikille napeille tyhjän label tekstin tai kuvan ja asetat takaisin aktiivisiksi, jos olet disabloinnut ne välttääksesi turhia painallus tapahtumia. Tietysti muista nollata myös muut pelin Tk:hon liittymättömät tietorakenteet ja muuttujat.

Macro [21.11.2009 16:45:59]

#

Niin juu... Empäs ajatellut tarpeeksi yksinkertaisesti. Miten asetan nappuloiden oletusväri? Gray on liian tumma ja white valkoinen.

Muokkaus. #ECE9D8 on oikea väri, mutta kun laitan sen configuressa taustaväriksi, niin viivojen väri tummenee samalla. Miten saisin palautettua ne normaaliin tilaan siis?

jalski [21.11.2009 20:38:54]

#

Älä turhaan hirveästi tappele vielä tässä vaiheessa nappuloiden värin tai muun kosmeettisen kanssa. Keskity saamaan ohjelman logiikka kuntoon.

Innostuin muuten itsekin tekemään ristinolla pelistä omaa versiota Infernolle Limbolla. Tällä hetkellä puuttuu vielä toinen pelaaja ja tarkistukset pelialueella.

Yhtenä esimerkkinä Infernolla kanavien käytöstä pelin toteutuksessa:

Molemmilla pelaajilla on oma kanavansa, mihin tulevia viestejä käsitellään pelin pääloopissa. Ongelmana on miten estää viestien käsittely muulloin, kuin pelaajan omalla vuorolla pelaajan painellessa peliruudukon nappeja?

Ratkaisu on yksinkertainen:

- määritellään pelaajille apukanavat ja dummy-kanava esim. :

p1ch := p1cmdchan; # p1cmdchan on kanava, mihin tulevat pelaajan viestit
dummypch := chan of string; # dummy-kanava

p2ch := dummyp2ch; # ykköspelaaja aloittaa

Peliloopissa käsitellään pelaajien viestit, mitkä tulevat p1cmdchan -ja p2cmdchan -kanaviin.

...
     p1cmd := <-p1ch =>
        p1ch = dummypch;
        (nil, tokens) := sys->tokenize(p1cmd, " ");
        case hd tokens {
           "b" =>
              x := int hd tl tokens;
              y := int hd tl tl tokens;

              # tähän tulisi viestin käsittely

        }
        absorb(p2cmdch); # poista odottava blokannut viesti
        p2ch = p2cmdch;  # anna vuoro kakkospelaajalle


    p2cmd := <-p2ch =>
        p2ch = dummypch;
        (nil, tokens) := sys->tokenize(p2cmd, " ");
        case hd tokens {
           "b" =>
              x := int hd tl tokens;
              y := int hd tl tl tokens;

              # tähän tulisi viestin käsittely

        }
        absorb(p1cmdch); # poista odottava blokannut viesti
        p2ch = p1cmdch;  # anna vuoro ykköspelaajalle


...

Tässä vielä koodi absorb-funktiolle:

absorb(ch : chan of string)
{
   for(;;)
   {
      alt
      {
         <- ch =>
            ;
         * =>
            return;
      }
   }
}

Macro [22.11.2009 12:05:35]

#

Olet oikeassa, ei kannata tehdä mitään hienouksia ennen kuin ohjelma toimii muuten kunnolla.

# -*- coding: UTF-8 -*-

#Lisätään ohjelmaan headerit
from Tkinter import *
import tkSimpleDialog
import tkMessageBox

root = Tk()
root.title("Ristinolla")

#Luetaan voittotiedot
def tilastot():
    global tiedot
    try:
        tiedosto = open("C:\\Program Files\\MPS\\Ristinolla\\tilastot.txt", "r")
        tiedot = tiedosto.readlines()
        tiedosto.close()
    except IOError:
        print "Tilastot.txt tiedostoa ei löytynyt"


tilastot()

#Pelin käynnistysfunktio
def kaynnista_peli():
    #Laitetaan muuttujat/taulukot globaaleiksi, jotta nämä näkyvät muuallakin kuin tässä funktiossa
    global kierros, napit, ruudut, ruutuja, root

    #Alustetaan root
    #root = Tk()
    #Alustetaan kierros, napit ja ruudut
    kierros = 0
    napit = {}
    ruudut = {}

    root.minsize(width=300, height=300)

    #Luodaan napit ja ruudut
    #Käydään napit pystysuunnassa
    for y in range(0, ruutuja):
        kehys = Frame(root)
        kehys.pack()

        #Käydään napit vaakasuunnassa
        for x in range(0, ruutuja):
            napit[(x, y)] = Button(kehys, text = "", command = nappula_painettu(x, y), width = 2)
            napit[(x, y)].pack(side = LEFT)

    #Käynnistetään
    mainloop()

#Nappulan koordinaatit
def nappula_painettu(x, y):
    return lambda: painettu(x, y)

#Tulostetaan tilanne
def tilanne(pelaaja):
    if pelaaja == "X":
        pisteet = tiedot[1]
    else:
        pisteet = tiedot[0].replace("\n", "")

    kehys = Frame(root)
    kehys.pack()

    teksti = Button(kehys, text=pelaaja + ": " + pisteet + " voittoa", borderwidth=0, command="", state=DISABLED)
    teksti.pack(side=LEFT)

#Näitä nappuloita on jo painettu
nappulat = []
#Tällä merkitään ristit/nollat
def painettu(x, y):
    #Asetetaan muuttujien näkyvyysalueet globaaleiksi
    global kierros, napit, ruudut, voitto

    #Jos kierros % 2 == 0, niin on nollan vuoro
    ruudut[(x, y)] = "OX"[kierros % 2]

    #print "Ruutu (%d, %d) on nyt %c" % (x, y, ruudut[(x, y)])
    #Asetetaan nappulan nimeksi uusi tieto, ja komennoksi false
    napit[(x, y)].configure(text = ruudut[(x, y)], state = DISABLED)
    #Lisätään kierroksia yhdellä
    kierros += 1

    #Tarkistetaan voitto
    if tarkista_voitto(x, y, ruudut[(x, y)]):
        #Disabloidaan kaikkien nappien komennot tyhjiksi
        for nappi in napit.itervalues():
            nappi.configure(text = "", width = 2, state = NORMAL)

        print "Peli loppui, %c voitti!" % (ruudut[(x, y)])
        tallenna("%c" % (ruudut[(x, y)]))

        voitto = True
    else:
        voitto = False

def tarkista_voitto(x, y, m):
    #Laitetaan muuttuja globaaliksi
    global voittopituus

    #Tarkistetaan
    if (pituus(x, y, 0, -1, m) + pituus(x, y, 0, 1, m) > voittopituus):
        return True
    if (pituus(x, y, -1, -1, m) + pituus(x, y, 1, 1, m) > voittopituus):
        return True
    if (pituus(x, y, -1, 0, m) + pituus(x, y, 1, 0, m) > voittopituus):
        return True
    if (pituus(x, y, 1, -1, m) + pituus(x, y, -1, 1, m) > voittopituus):
        return True
    return False

def pituus(x, y, dx, dy, merkki):
    #Globaaliksi muuttuja
    global ruudut

    lukema = 0
    while ruudut.has_key((x, y)) and ruudut[(x, y)] == merkki:
        x, y, lukema = x + dx, y + dy, lukema + 1

    return lukema

def tallenna(merkki):
    global x_tilanne, o_tilanne
    x_tilanne = int(tiedot[1])
    o_tilanne = int(tiedot[0].replace("\n", ""))

    if merkki == "O":
        o_tilanne += 1
        teksti.configure(text="O: " + str(o_tilanne) + " voittoa")
    else:
        x_tilanne += 1
        teksti2.configure(text="X: " + str(x_tilanne) + " voittoa")

    tiedosto = open("c:\\Program Files\\MPS\\Ristinolla\\tilastot.txt", "w")
    tiedosto.write(str(o_tilanne) + "\n" + str(x_tilanne))
    tiedosto.close()



#Tulostetaan tilanne

#Ruma tapa, korjaan kyllä =)
kehys = Frame(root)
kehys.pack()

teksti = Button(kehys, text="O: " + tiedot[0].replace("\n", "") + " voittoa", borderwidth=0, command="", state=DISABLED)
teksti.pack(side=LEFT)

kehys2 = Frame(root)
kehys2.pack()

teksti2 = Button(kehys2, text="X: " + tiedot[1] + " voittoa", borderwidth=0, command="", state=DISABLED)
teksti2.pack(side=LEFT)

ruutuja, voittopituus = 10, 5
kaynnista_peli()

Tältä näyttää tämän hetkinen ohjelmani. Siinä on jo jotain "hienouksia", kuten pisteiden kirjaus tiedostoon ja sieltä niiden tulostaminen. Ongelmana on, että jokin taulukko/muuttuja ei tyhjenny/tyhjennetä pelin lopussa, niin pelissä on näkymättömiä rasteja ja nollia. Siitä tulee harmeja, kun sopivasti onnistuu painamaan ruudulle. Keksittekö mitkä tarpeellista muuttujaa/listaa ei tyhjennetä? Minä en sitä löytänyt.

Ongelmana on se, että jos jossakin kohdassa on ollut 4 peräkkäistä rastia, ja laitetaan yksi rasti niin että tulisi 5 putkeen, niin pelaaja saa siitä pisteen!

Macro [22.11.2009 14:09:23]

#

Muuten, miten saan ohjelmassa toimimaan läpinäkyvän ikonin? Laitoin valkoisen värin läpinäkyväksi, mutta nyt se näkyy ohjelmassa oranssina.

Macro [22.11.2009 19:14:25]

#

Oliko edelliset kysymykset epäselviä, pitääkö selventää? =)

jalski [22.11.2009 21:02:54]

#

Mihin tarvitset ohjelmassa läpinäkyvyyttä?

Oma ristinolla toteutukseni rupeaa pikkuhiljaa hahmottumaan. Pelilaudan tarkistukset puuttuvat edelleen, mutta verkkopeli toimii. Toteutin tosin vasta serverin osuuden pelistä, mutta testasin toiminnan lähettämällä kakkospelaajan ohjausviestejä telnetillä ykköspelaajan serverille.

Laitan pelin valikkoon mahdollisuuden isännöidä peliä tai toimia asiakkaana, teen pelilaudan tarkistukset ja siistin koodia hiukan.

Olisi hauska nähdä muiden tekemiä ristinolla verkkopelitoteutuksia eri ohjelmointikielillä ja vertailla erilaisia ratkaisuja.

Macro [23.11.2009 08:50:51]

#

Sekin on sellainen lisäominaisuus, voisi pohtia vasta kun on muuten valmis.

Muokkaus. Nyt sitten ei ole yhtään virheitä enään pelissä, vielä se nettipeli osuus pitää saada valmiiksi. =)

jalski [23.11.2009 11:05:36]

#

En tiedä Pythonin Tk-toteutuksesta, mutta Infernon toteutuksessa voi käyttää panel widgettiä ja tähän saa kopioitua imagen Tk:n putimage -funktiolla. Imageen voi tietysti, piirtää käyttäen Draw-moduulia hyväkseen.

jalski [23.11.2009 20:19:27]

#

Joko olet ehtinyt toteuttamaan verkkopeliä?

Oma toteutukseni rupeaa olemaan loppusuoralla. Muutama dialogi-ikkuna on toteuttamatta ja joitain mahdollisia virhetilanteita vielä käsittelemättä.

Peli ei kyllä taida kauneuskilpailuja voittaa, mutta ajanee asiansa. Alla löytyypi kuva kehitysversiosta. Kuvassa kakkospelaajan komennot lähetetään telnetillä.

http://www.tip9ug.jp/who/jalih/ristinolla.jpg

Macro [23.11.2009 20:24:17]

#

En ole tänään ehtinyt sitä paljon miettiä, kun pitää tehdä esitelmä kouluun. Mutta juu, yhdellä koneella pelaus onnistuu suoraan sanoen mainiosti.

jalski [25.11.2009 22:49:25]

#

Peliin toteutettu mahdollisuus isännöidä peliä tai liittyä peliin. Oikeastaan enää puuttuu voittosuoran korostaminen eri värillä ja peliin liityttäessä dialogi-ikkuna, mihin syötetään isäntäkoneen osoite.

Infernon Limbo toteutuksessa koodia tällä hetkellä n. 500 väljää riviä ja valmis tuotos mahtunee n. 700 riviin.

Macro [27.11.2009 15:03:11]

#

Selevä. =) Tässä kun vihdoin aloitin verkkopeliversiota tekemään, niin jäi yksi asia päähän. Miten saisin valikkoon sellaisen ominaisuuden, että kun painetaan jotakin menua niin tulisi normaalisti se valikko, mutta jossain kohdassa olisi vielä lisävalintoja (Aukeaisi seuraava sivulle) jossa voisi valita yhden vaihdoehdon. En tiedä, että selitinkö järkevästi. Kuitenkin varmaan ymmärrätte tarkoituksen.

Muokkaus. Cascading menu-hakusanoilla löytyy jotain, katsompas...

Macro [27.11.2009 16:11:57]

#

No nyt löytyikin, mutta yksi ongelma ilmenee:

def valikko1():
    valikko1 = Menubutton(root, text = 'Ristinolla')
    valikko1.pack(side = LEFT)

    valikko1.menu = Menu(valikko1, tearoff = 0)
    valikko1.menu.valikko12 = Menu(valikko1.menu, tearoff = 0)

    valikko1.menu.valikko12.add_radiobutton(label = "Valitse vastustaja", state = DISABLED)
    valikko1.menu.valikko12.add_separator()


    for kaveri in kaverit:
        valikko1.menu.valikko12.add_radiobutton(label = kaveri, menu = valikko1.menu.valikko12.kaveri)

        valikko1.menu.valikko12.kaveri = Menu(valikko1.menu.valikko12, tearoff = 0)
        valikko1.menu.valikko12.kaveri.add_cascade(label = "Testi 1")
        valikko1.menu.valikko12.kaveri.add_cascade(label = "Testi 2")

    if len(kaverit) > 0:
        valikko1.menu.valikko12.add_separator()
    else:
        valikko1.menu.valikko12.add_radiobutton(label = "(Ei kavereita)", state = DISABLED)
        valikko1.menu.valikko12.add_separator()

    valikko1.menu.valikko12.add_cascade(label = 'Uusi pelikaveri', command = uusi_kaveri)

    valikko1.menu.add_cascade(label = 'Uusi peli')
    valikko1.menu.add_cascade(label = 'Yhdistä', menu = valikko1.menu.valikko12)
    valikko1.menu.add_separator()
    valikko1.menu.add_cascade(label = 'Lopeta')

    valikko1['menu'] = valikko1.menu
    return valikko1

Tällä tyylillä koittaisin tehdä valikon. Ongelmaksi tulee tuo for-silmukka. Ohjelman alussa on funktio, joka lukee tiedostosta kaverit.txt kaikki kaverit ja asettaa ne taulukkoon. Sitten pitäisi generoida automaattisesti tuonne valikkoon kohdat Testi 1 ja Testi 2.

Jos kavereita ei ole, valikon pitäisi näyttää tältä:

Ristinolla
	Uusi peli
	Yhdistä ->  (Valitse kaveri)
	----------  (Ei pelikavereita)
	Lopeta      ------------------
				Lisää uusi kaveri

Jos taas on kavereita vaikka kolme kappaletta, niin se olisi tälläinen:

Ristinolla
	Uusi peli
	Yhdistä ->  (Valitse kaveri)
	----------  Pekka
	Lopeta      Maija
				Antti
				-----------------
				Lisää uusi kaveri

Koodi generoi ensimmäiset valikot hyvin, mutta kun pitäisi kavereiden kohdalle tehdä tuollainen uusi valikko, niin tulee virheilmoitus. Miten korjaan tuon for-silmukan siten, että myös jälkimmäinen valikko (testi 1, testi2) tulee jokaisen pelaajan kohdalle? Nyt saan virheilmoitukset AttributeError: Menu instance has no attribute 'kaveri'

jalski [27.11.2009 22:05:47]

#

Tuon menu systeemisi rakennehan on alla olevan mukainen:

Ristinolla (menubutton)
   Uusipeli (command)
   Yhdistä (cascade)    --> (Valitse kaveri) (command)
   separator                Pekka (command)
   Lopeta (command)         Maija (command)
                            Antti (command)
                            separator
                            Lisää uusi kaveri (command)

Eli menubuttoniin on linkitetty menu, missä on: Uusipeli, Yhdistä, separator ja Lopeta. Yhdistä on cascade-tyyppinen ja siihen on linkitetty menu, missä on lista kavereista.

Kun tuota katsot, niin sen rakentaminen aika helppo jakaa vaiheisiin. Tuo kaverit menuhan on oikeastaan ainoa, mikä pitää muodostaa silmukassa dynaamisesti.

Metabolix [27.11.2009 22:29:45]

#

Termi valikko1.menu.valikko12.kaveri ei varmastikaan toimi, vaan oikea jäsen pitäisi hakea jollain funktiolla, joka ottaisi parametrikseen kaveri-muuttujan arvon. (Tuossa yrität hakea kirjaimellisesti jäsentä "kaveri".) Tähän varmaan löytyy dokumentaatiosta ratkaisu.

Macro [28.11.2009 09:26:02]

#

Koitin etsiä dokumentaatiosta neuvoa, mutta ei suoraan sanoen löytynyt mitään. Koitin myös tuota Metabolixin neuvoa, mutta en keksinyt mitä tarkoitit suoraan.

Metabolix [28.11.2009 09:48:54]

#

Tarkoitin siis, että valikko1.menu.valikko12.kaveri hakee jäsentä nimeltä "kaveri", kun pitäisi hakea esimerkiksi jäsentä "Uolevi". Jäsen pitäisi siis hakea jollain funktiolla: valikko1.menu.valikko12.hae_jasen(kaveri). Mutta unohda tämä.

Nyt kun tarkemmin katson, niin eihän tuossa koko kaverisilmukassa ole järkeä. Yrität käyttää arvoa valikko1.menu.valikko12.kaveri jo ensimmäisellä rivillä, vaikka asetat sen vasta seuraavalla. Lisäksi teet siitä valikon, vaikka eikö jokainen kaveri pitäisi lisätä yksinkertaisesti samalla tavalla kuin tuo ("Ei kavereita)"?

Seuraavaa ehtolausetta voisi myös yksinkertaistaa, kun molemmissa tapauksissa kutsutaan lopuksi add_separator.

Edit: Ainakin näin sain luotua mielestäni kelvollisen valikon:

from Tkinter import *

root = Tk()
nappi = Menubutton(root, text = "Ristinolla")
nappi.pack()
valikko = Menu(nappi, tearoff = 0)
kaverivalikko = Menu(valikko, tearoff = 0)
nappi["menu"] = valikko

valikko.add_command(label = "Yhdistä")
valikko.add_cascade(label = "Kaveri", menu = kaverivalikko)
kaverivalikko.add_command(label = "Pera")
kaverivalikko.add_command(label = "Tipsu")
kaverivalikko.add_separator()
kaverivalikko.add_command(label = "Uusi kaveri")

Muuta vain kaverien lisäämisen silmukaksi ja lisää toiminnot.

Macro [28.11.2009 12:02:32]

#

Pitäisikö funktion ottaa arvoksi kaverin nimi ja palauttaa se?

Koitin näin:

friends = []
friends.append("Mauri")
friends.append("Martti")

def nimi(pelaaja):
    return pelaaja

for friend in friends:
    kaverivalikko.add_cascade(label = friend, menu = nimi(friend))

    nimi(friend).add_command(label = "Pelaa")
    nimi(friend).add_command(label = "Poista kaveri")

Tästä saan vain virheilmoituksen, että str-objektilla ei ole add_command attribuuttia. Nyt olen kuitenkin lähempänä oikeaa tulosta, eikö vain?

vehkis91 [28.11.2009 12:57:00]

#

Eihän tuossa funktiossa ole mitään järkeä, koska se palauttaa parametrinsa suoraan, eikä tee mitään muutoksia siihen.

def nimi(pelaaja):
    return pelaaja

#esim nämä kaksi ajavat saman asian

joku = nimi(pelaaja)

joku = pelaaja

Chiman [28.11.2009 13:02:52]

#

Vehkiksen viestiin liittyen, tämä:

nimi(friend).add_command(label = "Pelaa")

...on arvolla "Mauri" sama kuin tämä:

"Mauri".add_command(label = "Pelaa")

Ja merkkijonolla (tässä "Mauri") ei ole metodia add_command.

En ole vuosiin koodannut Tkinterillä, joten en osaa äkkiseltään kertoa oikeaa koodia, mutta tämä näyttäisi hyvältä esimerkiltä: http://effbot.org/tkinterbook/menu.htm

Noista nimistä voi päätellä miten alavalikko ensin rakennetaan ja sitten kiinnitetään muuhun menuun.

Macro [28.11.2009 15:03:30]

#

Ei tuolla sivulla neuvottu mitään tähän ongelmaan. Uusia neuvoja?

Metabolix [28.11.2009 18:01:51]

#

Sait jo aiemmin koodin, joka liittää napinpainallukseen tietyn ruudun (koordinaatit). Samaa voit soveltaa tässäkin. Kannattaa käyttää nimen sijaan kaverin indeksiä listassa.

Yrität tunkea liikaa asioita Tk:n olioihin. Kaikki käy helpommin, kun säilytät kavereiden valikot erikseen.

kaverivalikot = []
for i in range(0, len(kaverit)):
  nimi = kaverit[i]
  valikko = Menu(...)
  kaverivalikot.append(valikko)

Macro [28.11.2009 18:20:10]

#

Mutta, tästä tulee taas se str-ongelma. Onnistun vaan muodostamaan tästä "kaveri".add_cascade(...).

Metabolix [28.11.2009 18:24:10]

#

Laita valikko.add_cascade. Tässähän on tarkoitus luoda joka kaverille uusi valikko, joka sitten lisätään vielä kaverivalikot-listaan siltä varalta, että sitä halutaan myöhemmin muokata.

Macro [28.11.2009 19:49:04]

#

Voitko näyttää? Ei onnistu nähtävästi, on tullut kohta kaksi tuntia tapeltua sen kanssa. Saan koko ajan sitä str-erroria, vaikka mitä tekisin.

jalski [28.11.2009 22:30:15]

#

Oma ristinollan Limbo toteutukseni Infernolle on nyt valmis. Julkaisen lähdekoodin täällä, kunhan saan sen siistittyä julkaisukuntoon.

Macro:
Kehotan edelleen keskittymään itse peliin ja jättämään hienot valikot ja muut loppuun, kun pelin runko on testattu toimivaksi. Edistyminen projektissa on näin nopeampaa ja mielenkiinto ei lopahda kesken kaiken valikkojen kanssa tapellessa.

Alla joitain kuvia omasta toteutuksestani:

http://www.tip9ug.jp/who/jalih/rn1.jpg
http://www.tip9ug.jp/who/jalih/rn2.jpg
http://www.tip9ug.jp/who/jalih/rn3.jpg

Macro [28.11.2009 22:57:51]

#

No, voisihan sen niinkin tehdä, mutta nyt ei juuri kiinnosta tehdä nettipeliversiota siitä. Kun saan tuon valikon kuntoon, niin katsotaan jaksanko alkaa toteuttaa nettiversiota.

Chiman [28.11.2009 23:20:35]

#

En ole aivan kärryillä mitä noista menukohdista halutaan tapahtuvan, mutta tässä sellaisenaan toimiva esimerkki funktiokutsuineen:

#!/usr/bin/python
# -*- coding: utf-8 -*-

from Tkinter import *

def valitse_kaveri(kaveri):
    # tee jotain fiksua tässä
    print "kaveriksi valittiin", kaveri

def lisaa_uusi_kaveri():
    # uuden kaverin lisäys
    pass

kaverit = ["Mauri", "Matti"]

root = Tk()
nappi = Menubutton(root, text="Ristinolla")
nappi.pack()
valikko = Menu(nappi, tearoff=0)
kaverivalikko = Menu(valikko, tearoff=0)
nappi["menu"] = valikko

valikko.add_command(label="Uusi peli")
valikko.add_cascade(label="Yhdistä", menu=kaverivalikko)

for kaveri in kaverit:
    kaverivalikko.add_command(label=kaveri, command=lambda a=kaveri:valitse_kaveri(a))

kaverivalikko.add_separator()
kaverivalikko.add_command(label="Lisää uusi kaveri", command=lisaa_uusi_kaveri)

root.mainloop()

Macro [28.11.2009 23:33:13]

#

Muokkaus. Kiitos Chiman, sain koodistasi vinkin miten toteutan valikon. =)

Chiman [28.11.2009 23:47:20]

#

Ehdit näköjään muokata jo viestiäsi, mutta siinä olleeseen rakenteeseen sopii tämä for-silmukka. Koodi muilta kohdilta sama kuin edellisessä viestissäni.

for kaveri in kaverit:
    kaveritoiminnot = Menu(kaverivalikko, tearoff=0)
    kaveritoiminnot.add_command(label="Poista", command=lambda a=kaveri:poista_kaveri(a))
    kaveritoiminnot.add_command(label="Yhdistä", command=lambda a=kaveri:yhdista_kaveri(a))
    kaverivalikko.add_cascade(label=kaveri, menu=kaveritoiminnot)

Macro [28.11.2009 23:48:19]

#

Jep, tajusin tämän melkein samantien, kun suoritin ohjelmasi. =) Helppoja asioita on vaikea keksiä. :D

Macro [29.11.2009 20:21:02]

#

Viidesti olen mukamas keksinyt, että miten saan saman ohjelman toimimaan ristinollapelissä verkossa. En millään keksi logiikkaa, että miten saan odotettua että toinen laittaa oman vuoronsa.

Metabolix, oletko tehnyt oman version tästä nettipelinä Pythonilla? Kiinnostaisi tietää, että miten teillä menee vastustajan siirron odotus ja vastaanottaminen. Se onkin se ainoa ja vaikein osuus siinä nettipelin teossa. En saa päähäni, että millaisen silmukan teen.

jalski [29.11.2009 20:32:04]

#

Pistä vaikka joku globaali-muuttuja, mikä ilmoittaa kumman vuoro on. Pelilaudan nappien callback-aliohjelmassa tarkistat alussa muuttujasta kumman vuoro on ja jos on oma vuoro, niin käsittelet painalluksen ja lopuksi vaihdat muuttujan tilaksi kaverin vuoron. Jos taas vuoro ei ole oma painalluksen tullessa, niin et tee mitään.

Postitin koodivinkkeihin oman Infernon Limbo toteutukseni ristinollasta. Jos tuo joskus hyväksytään koodivinkkeihin, niin voit ehkä saada siitä joitain ideoita.

Macro [29.11.2009 20:37:57]

#

Niin minulla olikin aluksi (if kierros % 2 == 0: ..., else: ...), mutta en keksi miten saan tehtyä sen lopun. Mitä laitan if-lauseiden jälkeen?

Muokkaus. Kyllähän sitä vinkkiäsi voi lukea. =) Helpottaisi varmasti aika kummasti, jos joku kokenut tekisi Pythonilla oman toteutuksensa malliksi. =)

jalski [29.11.2009 20:47:25]

#

Edelliseen viestiini viitaten, unohda ne kierrokset.

Esim. serverille: X aloittaa, alusta ohjelman alussa esim. globaali muuttuja vuoro = 1. Nyt ohjelmasi käsittelee paikallisen pelaajan painallukset. Kun painallus on käsitelty ja siirto on hyväksyttävä, niin lopuksi vaihdat muuttujaan 0. Kun kakkospelaajan siirto on käsitelty, sijoitat muuttujaan 1 ja näin annat vuoron takaisin paikalliselle ykköspelaajalle.

Macro [29.11.2009 20:49:32]

#

Ja pidän tämän kaiken while-silmukassa? Jos joku jaksaa/ehtii, niin mielelläni katsoisin Pythonilla toteutettua versiota.

jalski [29.11.2009 21:36:14]

#

En osaa nyt suoralta kädeltä antaa vastausta ongelmaasi. Näyttäisi siltä, että joudut nyt hiukan taas opiskelemaan miten sockettien käsittely toteutetaan Tkinterin kanssa.

Tuolla on ainakin jotain keskustelua aiheesta:
http://markmail.org/message/2t5uoke2nf4dxsca­#query: page:1 mid:2vyfilwsrhmeduwr state:results

Macro [30.11.2009 09:38:17]

#

Nyt sain toteutettua toiminnon siten, että yhteyden muodostus toimii. Ongelmana on vielä se, että miten tunnistetaan kumman vuoro on lähettää dataa. Yleensäkkin kun ohjelmoin, niin teen yksinkertaiseteun version siitä koko ohjelmasta, jolloin voin kokeilla sillä niin ettei kaikki asiaan kuulumattomat toiminnot ole siinä mukana. Nytkin on sitten niin. Minulla on muutama funktio:

client(): Ottaa yhteyden serveriin, jos valikosta otetaan client-tila käyttöön.
server(): Ottaa yhteyden clienttiin, jos valikosta otetaan server-tila käyttöön.
laheta(): Lähettää kummalle tahansa paramenttinä annetun datan, riippuen kumpaan on yhdistetty.
vastaanota_serverilta(): Käy silmukassa niin kauan läpi, kunnes saadaan dataa serveriltä.
vastaanota_clientilta(): Pyörittää silmukkaa, kunnes saadaan dataa.
close(): Sulkee yhteyden
chat(): Muodostaa yhteyden

# -*- coding: utf-8 -*-
import socket
from Tkinter import *
import tkSimpleDialog

#Alustetaan root
root = Tk()

#Vuoron vaihto onnistuu tällä
#1 = serveri (X), 0 = clientti (O)
vuoro = 1

def client():
    #Otetaan etäyhteys toiseen koneeseen
    serveri = ("91.152.133.214", 1337)
    #Sokketti valmiiksi
    sokketti = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sokketti.connect(serveri)

    #Viestin lähetys onnistuu serverille seuraavalla
    #sokketti.send(lähetettävä_data)

def server():
    #Otetaan paikallinen yhteys localhostiin
    serveri = (socket.gethostname(), 1337)
    #Sokketti valmiiksi
    sokketti = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sokketti.bind(serveri)

    #Odotetaan klienttiä
    sokketti.listen(1)
    #Hyväksytään yhteys
    asiakas, osoite = sokketti.accept()

    #Status...
    teksti.configure(text = "Yhdistetty!")

    #Datan lähettäminen:
    #asiakas.send(lähetettävä_data)

def laheta(data):
    #Lähetetään siirtodata serverille/klientille
    sokketti.send(data)

def vastaanota_serverilta():
    #Pyöritetään silmukassa
    while True:
        #Vastaanotetaan siirtodata
        siirto = sokketti.recv(1024)

        #Jos sitä ei ole, break
        if not siirto:
            break
        else:
            return siirto
            vuoro = 0
            break


def vastaanota_clientilta():
    #Pyöritetään silmukassa
    while True:

        #Vastaanotetaan siirtodata
        siirto = asiakas.recv(1024)

        #Jos sitä ei ole, break
        if not siirto:
            break
        else:
            #Muuten palautetaan arvo ja break
            return siirto
            vuoro = 1
            break

def close():
    try:
        #Suljetaan yhteys
        sokketti.close()
    except:
        teksti.configure(text = "Virhe sokketin sulkemisessa!")

def yhteys(serveri):
    if serveri == True:
        #Jos ollaan serveri, niin vastaanotetaan viestit klientiltä
        teksti.configure(text = "Odota, odotetaan vastustajaa...")
        server()
    else:
        #Ollaan klient, vastaanotetaan serveriltä
        teksti.configure(text = "Odota, yhdistetään serveriin!")
        client()
        teksti.configure(text = "Yhdistetty!")

def chat(toiminta):
    if toiminta == "server":
        #Toimitaan serverinä
        #Yhteys muodostetaan asiakkaaseen

        yhteys(True)

        palautus = vastaanota_clientilta()

        teksti.configure(text = "Client sanoo: " + palautus)
    else:
        #Toimitaan klienttinä
        #Yhteys muodostetaan serveriin

        yhteys(False)
        palautus = vastaanota_serverilta()

        teksti.configure(text = "Serveri sanoo: " + palautus)

valikko = Menu(root, tearoff = 0)
yhteystapa = Menu(valikko, tearoff = 0)

valikko.add_cascade(label = "Ristinolla (BETA)", menu = yhteystapa)

yhteystapa.add_radiobutton(label = "Isäntäkone", command = lambda: chat("server"))
yhteystapa.add_radiobutton(label = "Client", command = lambda: chat("client"))
yhteystapa.add_command(label = "Sulje yhteys", command = close)

root.config(menu = valikko)

teksti = Label(root, text = "Viesti...")
teksti.pack(side = LEFT)

mainloop()

Vastaanotto funktioissa on jotain vikaa, koska antaa virheen datan vastaanottamisessa. Global name sokketti/asiakas* is not defined

* Riippuu tilasta.

Muokkaus. Tuo on muuten aika buginen testiohjelma.

Metabolix [30.11.2009 11:38:50]

#

Kun yhteys on muodostettu, palvelimen täytyy ilmoittaa pelimerkit ja aloittajan (esim. "SX CO X"). Tämän jälkeen silmukka voi molemmilla toimia näin:

kun peli on kesken:
    jos vuoro == oma:
        odota nappia
        lähetä siirto
    muuten:
        odota dataa

Macro [30.11.2009 17:38:53]

#

Jep. Kokeilin ihan komentorivipohjaisesti, että saanko kysyttyä vuorotellen viestiä ja lähetetty & tulostettua sen. Näin sain toteutettua jotain, joka kyllä jää jumiin toiseen while-lauseeseen.

# -*- coding: utf-8 -*-
from Tkinter import *
import socket

tyyli = raw_input("Serveri = s, client = c: ")
vuoro = 1

if tyyli == "s":
    serveri = (socket.gethostname(), 1337)
    sokketti = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sokketti.bind(serveri)

    print "Odotetaan asiakasta..."
    sokketti.listen(1)
    laheta, osoite = sokketti.accept()
    print "Yhdistetty!"

else:
    ip = raw_input("Serverin ip-osoite: ")
    serveri = (ip, 1337)

    print "Yhdistetään..."
    laheta = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    laheta.connect(serveri)
    print "Yhdistetty!"

if tyyli == "c":
    vuoro = 2

def mainloop():
    global vuoro, tyyli
    if vuoro % 2 == 0:
        return "X"
    else:
        return "O"

    vuoro += 1
    print vuoro

print "Sinä olet " + mainloop()
while True:
    pelaajan_vuoro = mainloop()
    if pelaajan_vuoro == "X":
        viesti = raw_input("Viesti: ")
        laheta.send(viesti)
    else:
        while True:
            try:
                data = laheta.recv(1024)
            except:
                print "Virhe datan vastaanottamisessa!"
                break

            if not data:
                break
            else:
                print "Viesti: " + data

Nyt tämä kysyy viestiä vain toiselta (client) koko ajan. Clientti on aina nolla, kun taas serveri on aina x. Eli se kohta toimii. Mitä pitää muokata, että kummatkin voivat vuorotellen kirjoittaa?

jalski [30.11.2009 19:59:08]

#

Joudut muuten oikeasti miettimään sockettien käyttämisen uudelleen tehdessäsi event-pohjaista ohjelmaa Tkinterin kanssa. Createfilehandler olisi vissiin helpoin tapa, mutta ei ole tuettuna Windows-alustalla enää. Tcl socketit näyttäisi olevan yksi suosittu tapa. Säikeiden käytössä taas pitää olla tarkkana ja noudattaa tiettyjä sääntöjä.

Allaolevasta voisi olla apua, katso erityisesti: Time, Tools, Threads and Animation -kappale.

http://codeidol.com/python/python3/A-Tkinter-Tour,-Part-2/

Macro [30.11.2009 20:01:52]

#

Uskon kyllä, että kun tuon saan tehtyä tekstipohjaisesti, niin kyllä se onnistuu myöhemmin Tkinterilläkin. =) Yksi asia kerrallaan...

jalski [30.11.2009 20:17:28]

#

Ok, kunhan varmistin, että tiedostat asian...

Tuosta tekstipohjaisesta versiosta: laita serverille ja asiakkaalle datan vastaanotto omiin säikeisiinsä. Tee lukitukset (lähetysvuorojen jako) serverin ja asiakkaan datan lähetysosassa.

Macro [30.11.2009 20:26:25]

#

Mitä nämä säikeet oikeen tarkoittavat? Miten ne tehdään? Koitin tehdä siten, että kun viestin on lähetetty, niin siirrytään seuraavaan osaan (vastaanottaminen), ja kun viesti tulee, niin sitten saa taas kirjoittaa. Koodin näetkin sivulla kaksi.

jalski [30.11.2009 20:38:36]

#

Tuli näköjään kiirus vielä työhommiin, joten en kerkiä nyt selittämään. Hae sanoilla Python sockets threads.

Tuolla oli jotain: http://ilab.cs.byu.edu/python/threadingmodule.html

Metabolix [30.11.2009 21:54:09]

#

Moniajo tarkoittaa sitä, että tietokone tekee useaa asiaa "samaan aikaan" (käytännössä usein vuorotellen). Prosessi tarkoittaa yleensä yhtä ohjelmaa. Sama ohjelma voi toimia useassa säikeessä, jolloin se näyttää tekevän asioita samaan aikaan. Käyttötarkoituksia on monia, ja nyt moniytimisten prosessorien yleistyttyä kotikoneillakin saadaan esimerkiksi raskaat pelit toimimaan nopeammin, kun kaksi säiettä pystyy ihan oikeastikin toimimaan samaan aikaan.

Säikeiden käyttö ei ole mutkatonta. Joissain kielissä on tähän kyllä erittäin yksinkertaiset menetelmät (mm. Limbossa ja Javassa), mutta useimmissa vastuu on yhä ohjelmoijalla. Ongelmia tulee nimittäin siinä vaiheessa, kun kaksi säiettä yrittää käyttää samaa muuttujaa.

x = 1
A  lukee muistista muuttujan x arvon (1)
A  korottaa luettua arvoa yhdellä (-> 2)
 B lukee muistista muuttujan x arvon (1)
 B korottaa luettua arvoa yhdellä (-> 2)
 B tallentaa muistiin arvon x = 2
A  tallentaa muistiin arvon x = 2

Molemmat korottivat arvoa, mutta lopulta arvo kasvoikin vain yhdellä, koska korotukset tapahtuivat samaan aikaan.

Tällaisten tilanteiden takia data täytyy lukita. Kun toinen säie havaitsee, että tarvittava data on lukittu, se pysähtyy odottamaan, kunnes data vapautuu.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import threading

lukko = threading.RLock()
yhteinen_data = 1

def funktio():
	# Lukitaan lukko ennen yhteisen datan käyttöä.
	lukko.acquire()
	yhteinen_data += 1
	lukko.release()

# Aloitetaan kaksi (samanlaista) säiettä.
threading.Thread(target = funktio).start()
threading.Thread(target = funktio).start()

Macro [02.12.2009 15:01:24]

#

Tämän pari päivää olen tässä pähkäillyt, mutta ei tuota tulosta. Höh!

Metabolix [02.12.2009 15:26:19]

#

Toinen vaihtoehto on tarkistaa data ei-blokkaavasta yhteydestä tietyin väliajoin.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import Tkinter
import socket

root = Tkinter.Tk()
yhteys = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
yhteys.connect(("localhost", 1234))

# Käytetään lyhyttä timeouttia, jotta datan puuttuminen aiheuttaisi vain
# poikkeuksen socket.timeout. Toinen vaihtoehto olisi yhteys.setblocking(False),
# jolloin datan puuttumisesta tulisi socket.error kuten muistakin virheistä.
yhteys.settimeout(0.001)

def paasilmukka():
	# Jatketaan "silmukkaa" tulevaisuudessakin.
	# Seuraava kierros 100 millisekunnin kuluttua.
	root.after(100, paasilmukka)

	if yhteys != None:
		lue_data()

def lue_data():
	global yhteys
	try:
		data = yhteys.recv(1024)
	except socket.timeout:
		return
	if len(data) == 0:
		yhteys = None
		print "Yhteys katkaistu!"
		return
	print("Saatiin '%s'" % data)

# Aloitetaan "silmukka".
root.after(100, paasilmukka)
root.mainloop()

Macro [02.12.2009 15:42:23]

#

Ongelmana oikeastaan on se, että miten saan clientin/serverin vuorotellen odottamaan dataa ja muutoin lähettämään dataa, ja vielä samassa ohjelmassa (Ensin valitaan toimitaanko serverinä vai clienttinä)!

Metabolix [02.12.2009 15:49:10]

#

Piirrä pelin toiminnasta kaavio (hieman tähän tapaan), niin asia ehkä selkenee.

def peli_alkaa_palvelimella():
  global oma, vuorossa
  vuorossa = "X"
  oma = "X"
  vastustaja = "XO".replace(oma, "")
  yhteys.send(vastustaja)
  yhteys.send(vuorossa)

def peli_alkaa_asiakkaalla():
  global oma, vuorossa
  # Tämä data on saatava heti, timeout pois.
  yhteys.settimeout(None)
  oma = yhteys.recv(1)
  vuorossa = yhteys.recv(1)
  yhteys.settimeout(0.001)

def paasilmukka():
  if peli_kaynnissa:
    if vuorossa <> oma:
      # Vastustajan vuoro, katsotaan, onko siirto tullut.
      yrita_vastaanottaa_siirto()

def klikattu(x, y):
  if vuorossa <> oma:
    # Ei ole oma vuoro, ei käsitellä klikkausta.
    return
  ilmoita_siirto_ja_vastaanota_ok()

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta