Jogo da velha e threads

Estou criando um joguinho da velha em java. Os jogadores podem ser:

  1. humano
  2. computador burro(escolhe aleatoriamente uma posição vazia e joga ali)
  3. agente inteligente
    criei uma interface comum a todos os jogadores, assim eu posso manipular o jogo sem me preocupar se os jogadores são agentes, humanos ou burros.
    a interface é:
    import java.awt.event.ActionEvent;

public interface Jogador extends Runnable {
public boolean efetuaJogada();
public void eventoJogador(Jogador jogador, ActionEvent evento);
}
o método efetuaJogada() é chamado pela engine do jogo, é ele que vai, não interessa como, selecionar uma das posições disponíveis e jogar nela. no caso do jogador burro, esse método escolhe uma casa livre aleatoriamente. no caso do jogador humano, esse método vai fazer a jogada aonde o jogador escolheu.
Daí em tenho, então, 3 classes que implementam essa interface: JogadorBurro, JogadorHumano e, ainda não implementada, JogadorAgente.
O método eventoJogador é chamado em ambos os jogadores(o ‘X’ e o ‘O’) sempre que vc clica em uma das posições do jogo(o tabuleiro do jogo da velha é representado por 9 JButton’s, dispostos como em uma matriz 3x3). O parametro ‘jogador’ é uma referencia ao objeto que representa o jogador atual no momento em que houve a ação, e o parametro ‘evento’ é o ActionEvent gerado quando há ação de clique em um desses 9 JButtons.
Quando vc inicia uma nova partida, é rodado um algoritimo assim:

for (int i = 0; i < 9; ++i) {
 jogador[jogadorAtual].efetuaJogada(); /* os dois jogadores estão armazenados em um array de duas posições, e há uma variavel inteira chamada jogadorAtual que indica o indice do jogador que possui a vez de jogar */
 if (jogadorAtual == 0)
  jogadorAtual = 1;
 else
  jogadorAtual = 0;
}
sempre que é chamado o método efetuaJogada() do Jogador, é verificado se ocorreu essa vitória. se ocorreu, esse laço for aí de cima é interrompido.

Bom, toda essa enrolação até agora foi necessária para descrever a minha atual situação, para que assim haja a possibilidade de me indicarem uma solução. Agora vamos ao meu problema:
No jogador humano… no método efetuaJogada(), esse método só pode retornar depois que duas váriaveis que existem nessa classe, chamadas x e y(ambas são int) forem preenchidas. Esse preenchimento ocorre no método eventoJogador(), que vai analizar qual posição o usuario clicou e ver se há possibilidade de jogar ali(verifica se a posição está vazia). se houver possibilidade, ele preenche as tais variáveis x e y com as coordenadas adequadas. Aí então o método efetuaJogada(), que estava esperando, vai estar apto a finalmente efetuar a jogada.
no método efetuaJogada(), eu fiz assim para implementar essa espera:
while (x == -1); // usem -1 para indicar q a variável nao está preenchida
mas aí toda a execução ficou parada nesse ponto, nao era possivel clicar nos botões, consequentemente, nao seria gerado o ActionEvent, consequentemente nao seria chamado o método eventoJogador(), e assim o programa continuaria parado.
Foi então que pensei em usar threads(é por isso que a interface Jogador está estendendo a interface Runnable), para que apenas a thread do jogador ficasse parada esperando, mas o resto do programa poderia continuar. mas ainda assim nao funcionou.
será que alguém pode me dar uma ideia de como resolver isso?

Cara sinceramento acho que vc está complicando seu algoritmo! Não vi necessidade nenhuma em colocar threads para o jogado! Afinal cada um joga separadamente! Uma hora é um jogador que joga outra hora é o outro.

Seria mais simples vc reestruturar seu algoritmo de outra forma, tipo assim:

Um listener para captar os clicks! depois dos clicks captados a posição seria validada! e ai vc chamaria ou não a função jogar que se encubiria apenas de marcar no tabuleiro! Quando o jogador fosse o computador a funcao jogar não seria chamada via click do mouse! mas sim pelo algoritmo inteligente!

O seu problema está na modelagem do seu sistema! Reestruture-o melhor!

eu tentei fazer sem threads, nao rolou.
eu imagino q, como a interface do jogo vai rodar ao mesmo tempo em que o objeto Jogador tem q ficar parado esperando, então achei necessário o uso de threads.
essa forma que vc falou, é mais ou menos assim que eu sempre usei qnd eu queria criar um jogo da velha em alguma linguagem. mas quero tentar nao fazer assim, pois quero que a engine(acho q engine é um termo mto pesado pra um simples jogo da velha hehehe) dê um tratamento igual para qq jogador, independente se o jogador é humano ou é computador. desta forma, se eu quiser futuramente adicionar um outro tipo de jogador, basta criar uma classe para ele que implemente a interface Jogador, adicionar a referencia a ela na engine, e pronto.

Não entendi sobre o tratamento igual para o humano ou o computador. Veja bem, repare que um jogo de xadrez para computador, você joga e na vez do computador ele já responde a jogada imediatamente a você, não é necessário um tratamento igual. O computador só precisa o tempo de processar a jogada, o que é relativamente muito rápido, por isso eu concordo com o maxwell_monteiro de que você precisa reestruturar o seu sistema e não usar threads.

eu poderia mto bem fazer:

inicio
se tipoJogador1 = humano então {
 espera usuário indicar a sua jogada
 enquanto a jogada nao for valida
  espera usuario indicar a sua jogada
 efetua a jogada indicada pelo usuário
}
senao, se tipoJogador1 = computador então {
 sorteia uma jogada
 enquanto a jogada nao for valida
  sorteia uma jogada
 efetua a jogada sorteada
}
se tipoJogador2 = humano então {
 espera usuário indicar a sua jogada
 enquanto a jogada nao for valida
  espera usuario indicar a sua jogada
 efetua a jogada indicada pelo usuário
}
senao, se tipoJogador2 = computador então {
 sorteia uma jogada
 enquanto a jogada nao for valida
  sorteia uma jogada
 efetua a jogada sorteada
}
se o jogo nao terminou entao
 salte para 'inicio'

vê? eu tenho que ficar vendo qual o tipo do jogador(humano, computador, …), e fazer um tratamento especifico para cada tipo.

eu fiz o jogo do jeito q eu indiquei, pois assim nao me interessa nem um pouco o tipo dos jogadores… desde que o jogador seja algo que implemente a interface Jogador, eu espero que o método efetuaJogada() vai se encarregar de criar uma jogada(seja a jogada q o usuario indicou, ou uma jogada sorteada), verificar se a jogada é legal, etc.
pra um simples jogo da velha, isso n vai fazer diferença… mas acho que é bom ir treinando essa abordagem, pois em sistemas mais complexos isso pode facilitar e mto a inclusão de coisas novas no sistema.