Archief - Online Tetris in Java: hulp gevraagd

Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.

forloRn_

Legacy Member
Ik en een klasgenoot van me zijn bezig aan een projectje: een online versie van het gekende spelprogramma Blokken.

De netwerkverbinding is al tot stand gebracht via RMI (Remote Method Invocation) en de Tetris-module (mijn gedeelte) is ook bijna afgewerkt.

De Tetris bestaat natuurlijk uit een tweedimensionale array (20 rijen op 10 kolommen) van integers, waarbij een 0 staat voor een leeg vakje, en een 1 en een 2 staan voor een vakje dat bezet is door een tegeltje van respectievelijk speler 1 en speler 2. Als er twee blokjes gevallen zijn kan de array er dus zo uitzien:

Code:
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0100002200
0111002200

Een vallend blokje kan gemanipuleerd worden met behulp van bepaalde methodes: requestMoveLeft(), requestMoveRight(), requestMoveDown() en requestTurn(). Hiermee wordt de array dus effectief aangepast. Ik heb de methodes request... genoemd omdat niet elke beweging op elk moment mogelijk is. Als er bijvoorbeeld rechts geen plaats is, kan het blokje niet naar rechts bewegen. De methodes canMoveLeft(), canMoveRight(), ... leveren true op als de overeenkomstige beweging mogelijk is.

Tot zover geen enkel probleem, en de single player-versie werkt dan ook perfect. De problemen treden pas op wanneer twee spelers tegen elkaar spelen over het internet.

Beide spelers hebben elk lokaal een JPanel ter beschikking dat hun keypresses registreert met behulp van een KeyListener. Die KeyListener roept dan de bovenstaande requests op.

Met het oog op snelheid denk ik dat het beter is dat er geen gemeenschappelijke Tetris-array gebruikt wordt, maar wel voor elke speler één (identieke) lokale. Een keypress van een speler moet namelijk onmiddellijk gevolgd worden door een aktie op de array, en geen omweg maken via het internet, wat zorgt voor vertraging.

Probleem: speler 2 moet op zijn scherm evenwel in real time (of toch bijna) kunnen volgen wat speler 1 doet, zonder dat speler 1 er zelf last van heeft!

Nu had ik gedacht aan het volgende: lokaal een soort van event-queue maken, die alle bewegingen van speler 1 opneemt, en een nieuwe thread creëren die de bewegingen in de queue asynchroon doorgeeft aan speler 2, en dus zijn array aanpast.

Is dit een goede manier of maak ik het veel te moeilijk? Ik ben niet op zoek naar code (die kan ik zelf wel produceren), maar wel naar een werkwijze.

Dankuwel.

walvis

Legacy Member
kan mischien helpen probeer ipv. met 3 variablen te werken
een soort van object te maken.(uw blok object).
dan hebt ge nen array van 20*10 blok-objecten.
je kan aan deze objecten vb de eigenschap geven speler: 1/2 leeg: ja neen.
wat een probleem kan geven is dat ge die gemeenschappelijke panel gaan gebruiken op het zelfde moment.
ge laat ze beter om beurt spelen denk ik

wlibaers

Legacy Member
Je moet op een of andere manier voor synchronizatie zorgen. Stel dat je de volgende situatie hebt:

0000000000
0011022000
0011022000
0000000000

En speler 1 wil naar rechts, en op hetzelfde moment wil speler 2 naar links. Door lag weten ze niet op tijd van elkaar wat ze doen, dus in de lokale kopie van de array zijn die bewegingen OK. Als ze dan hun beweging naar elkaar versturen, dan zal in beide gevallen de beweging vanop de andere PC geweigerd worden, want onmogelijk door de beweging vanop de eigen PC.

Voor speler 1 zal het scherm er dan zo uitzien:

0000000000
0001122000
0001122000
0000000000

En voor speler 2:

0000000000
0011220000
0011220000
0000000000

Je zal toch een manier moeten vinden om een van de twee mogelijkheden te laten overwinnen, want als het zo gebeurt zitten ze eigenlijk een verschillend spel te spelen. En zo'n fout van 1 blokje kan snel grote effecten hebben op het verdere verloop van het spel.

Bavo_acku

Legacy Member
@de laatste: dat is zijn probleem niet, dit soort Tetris wordt niet tegelijk gespeeld.

Het probleem is mij bekend, ik heb ook aan een Java tetris gewerkt met RMI. Jammer genoeg was het eerst een singleplayer versie waar ik dan multiplayer van wou maken, wat nogal een slecht design opleverde (mede dankzij slechte planning).

Ik probeerde bij elke event die informatie door te sturen, wrapped in een klasse die beschreef wat er gebeurd was en op welk blok. Dat is wezenlijk genoeg informatie, maar het rekent erop dat de andere client die informatie goed verwerkt. Een enkele fout en alles gaat de mist in (je stuurt eenmalig welk blok er gegeneert is en dan telkens welke move).

Ik had enkele bedenkingen bij RMI, namelijk de serialisatie. Het is essentieel dat je vooraf goed afbakent hoe uw objecten eruit zien die je doorstuurt. Zonder dat je er erg in hebt stuurt het namelijk al zijn associaties mee (transient is het keywoord hier dus).

Uw idee van een queue is behoorlijk goed. Ik neem wel aan dat je een aparte server ertussen zitten hebt (ook al zit die naast een client). Een thread die de events asynchroon afwerkt lijkt me elegant en stabiel. Uiteindelijk wil je enkel een preview, als je Tetrinet bekijkt (althans de oudere versies) dan is die preview ook maar semi-accuraat wegens performatie-overwegingen. De server ertussen weet wel exact wat wanneer is gebeurd bij twijfel.

forloRn_

Legacy Member
Die queue was eigenlijk helemaal niet nodig. Het is eenvoudiger (en veiliger) om gewoon de hele array integraal van speler 1 naar speler 2 te sturen. Hier vind je de code.

De klasse MoveSender is een subklasse van Thread.

De methode add(move[][]) kopieert de array move naar currentMove (een attribuut van MoveSender), print de array en zet de boolean moved op true.

De thread MoveSender wordt gestart met ms.start(). Wanneer moved op true komt te staan, zal de MoveSender currentMove afdrukken op het scherm, en moved weer op false zetten. In een later stadium wordt dan de code geïmplementeerd waarmee de array over het netwerk wordt verzonden.

De main()-methode bevat buiten een andere thread Adder niets speciaals. Deze thread wijzigt de array t[][] simpelweg 5 keer met tussenpozen van één seconde, en laat deze versturen door MoveSender.

Nu is er nog een klein probleempje: de MoveSender stopt niet. Hij zou moeten stoppen wanneer sending op false komt te staan:
Code:
public void run() {
	while(sending) {
		send();
	}
}

Weet er iemand de oplossing? Ik ben er nu al een tijdje naar aan het staren en ik zou echt niet weten wat er mis is. Als ik
Code:
sending = false;
achteraan in send() zet in plaats van in main(), stopt de MoveSender wel (om evidente redenen al na één maal, maar hij stopt).

Help!

Bavo_acku

Legacy Member
Ik probeerde te debuggen maar de code deed PC continu hangen. Er scheelt iets met de synchronisatie bij de wait();
Als je er een System out of counter zet dan looped het onnozel

edit: wat bij nader inzien was omdat je geen {} zet volgende de coding conventies

Bavo_acku

Legacy Member
Code:
	while(!moved && !done) {
	   System.out.println("moved = " + moved);
                wait(1000);
            }

done op true zetten als add gedaan is.

Uw thread zat vast op wait() na vijf sends terwijl de andere niet meer kon notifyen (hem wekken) wegens exited.

forloRn_

Legacy Member
Whoa, niet te rap. Die adder-thread kon MoveSender niet meer notifyen? Waarom werkt het wel als ik
Code:
sending = false;

op het einde van send() zet?

Bavo_acku

Legacy Member
Omdat uw movesender-thread dan niet in de 6de iteratie komt waar er geen informatie meer is, en waar ie dus niet meer gevraagd wordt wakker te worden uit zijn wait() (en mocht dat gebeuren de move boolean zou hem er weer in duwen anyway).

Als je dat daar op false zet blijft ie uiteraard op false want je herzet het veld enkel eens op het eidne van uw andere.

Dit is logisch te volgen met System.out, try it, count it en je zult het wel inzien.

edit: ik ga er bij uw code wel vanuit dat je weet wat object-locking en synchronisatie is, omdat ze zo elegant toegepast is (buiten het ene euvel dus).

walvis

Legacy Member
der zijn dus nog meer mensen hier op het forum dat java kunnen.
dacht dat het niet zo populair was.

off topic vraagske
ik werk nu met j# (.net componenten en gui creeren) is het mogelijk om u exe's die ge daarmee maakt om die te laten draaien op andere pc's zonder .net geïnstaleerd te hebben?

Bavo_acku

Legacy Member
walvis zei:
der zijn dus nog meer mensen hier op het forum dat java kunnen.
dacht dat het niet zo populair was.

off topic vraagske
ik werk nu met j# (.net componenten en gui creeren) is het mogelijk om u exe's die ge daarmee maakt om die te laten draaien op andere pc's zonder .net geïnstaleerd te hebben?

Ik neem aan dat het met de CLR werkt (zoals java's JRE) dus denk van niet. Terwijl wil ik van de gelegenheid gebruik maken om u te vragen: WHY?

Java's voordeel is weg met J# (independancy, opensource-minded e.a). .NET voordelen kan je niet volledig benutten omdat J# niet echt prioritair is in .NET, daar heb je C# voor (dat zowat even elegant is als Java).

Stap dus asap over naar Java of C#, anders verdoe je uw tijd imo.

forloRn_

Legacy Member
Goed, ik begin het te snappen. Er moest nog een notify() optreden om de thread wakker te maken, en hij mocht niet meer terug in wait() terechtkomen. De code is geüpdatet. Is dit een elegante oplossing?

Ik heb (nog) weinig ervaring met threads. Het boek dat ik gebruik is vrij karig met informatie. Het veronderstelt een minieme basis van de onderwerpen die erin besproken worden, maar voordien was ik nooit met threads in aanraking gekomen.

Bavo_acku

Legacy Member
forloRn_ zei:
Goed, ik begin het te snappen. Er moest nog een notify() optreden om de thread wakker te maken, en hij mocht niet meer terug in wait() terechtkomen. De code is geüpdatet. Is dit een elegante oplossing?

Ik heb (nog) weinig ervaring met threads. Het boek dat ik gebruik is vrij karig met informatie. Het veronderstelt een minieme basis van de onderwerpen die erin besproken worden, maar voordien was ik nooit met threads in aanraking gekomen.

Mijn loop werkte ook al, maar deed wel onnodig checks. Uw versie ziet er goed uit, paar keer goed testen zou ik zeggen. Threading kan serieus veel hoofdpijn opleveren.
Het zou leuker zijn mocht je visueel kunnen zien waar de threads in uw code zitten en doorwandelen. Nu is het een spel van strategisch debuggen en goed onthouden.

walvis

Legacy Member
Bavo_acku zei:
Ik neem aan dat het met de CLR werkt (zoals java's JRE) dus denk van niet. Terwijl wil ik van de gelegenheid gebruik maken om u te vragen: WHY?

Java's voordeel is weg met J# (independancy, opensource-minded e.a). .NET voordelen kan je niet volledig benutten omdat J# niet echt prioritair is in .NET, daar heb je C# voor (dat zowat even elegant is als Java).

Stap dus asap over naar Java of C#, anders verdoe je uw tijd imo.
jep i know ma school he
eerst java geleerd.
en ze gebruiken nu J# om te leren werken met visual studio en niet direct andere programeertaal te gebruiken.
volgend jaar c#

Bavo_acku

Legacy Member
walvis zei:
jep i know ma school he
eerst java geleerd.
en ze gebruiken nu J# om te leren werken met visual studio en niet direct andere programeertaal te gebruiken.
volgend jaar c#

i c, makes sense.
Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.
Terug
Bovenaan