Как заставить игрока и боты выбрать карты, чтобы передать им игрока по часовой стрелке?Javascript

Форум по Javascript
Ответить Пред. темаСлед. тема
Anonymous
 Как заставить игрока и боты выбрать карты, чтобы передать им игрока по часовой стрелке?

Сообщение Anonymous »

Я пытаюсь выполнить задание, в котором я должен создать карт Game Game of Hearts. Кнопка «пройти». Тем не менее, я не могу заставить кнопку прохода появиться. Я также считаю, что должен поместить каждую карту в контейнер, чтобы сделать их кликабельными, но я не на 100%, как это реализовать. Я должен только обновлять представление, а не модель или контроллер.
Вот модель:

Код: Выделить всё

import {HU} from "./hearts_utils.js";

export class HeartsModel extends EventTarget {
#playerNames;
#state;
#scorelog;
#hands;
#current_trick;
#collected_tricks;
#passing;

constructor() {
super();

this.#playerNames = {
north: null,
east: null,
south: null,
west: null
};

this.#state = 'uninitialized';

this.#scorelog = [];

this.#hands = {
north: null,
east: null,
south: null,
west: null
};

this.#collected_tricks = {
north: [],
east: [],
south: [],
west: []
};
}

// Private methods
#dealCards () {
let deck = [];
['spades', 'hearts', 'diamonds', 'clubs'].forEach(suit => {
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14].forEach(rank => {
deck.push(new Card(suit, rank));
})
});
deck = deck.map((card) => [card, Math.random()]).sort((a,b) => a[1]-b[1]).map((cpair) => cpair[0]);
return {
north: new Hand(deck.slice(0,13)),
east: new Hand(deck.slice(13,26)),
south: new Hand(deck.slice(26,39)),
west: new Hand(deck.slice(39,52))
};
}

// These methods are only available to controller to update model.
// They should never be called from view objects.

initialize (north_name, east_name, south_name, west_name) {
if (this.#state != 'uninitialized') return;

this.#playerNames.north = north_name;
this.#playerNames.east = east_name;
this.#playerNames.south = south_name;
this.#playerNames.west = west_name;

this.#scorelog = [];
this.setupGame('right');
};

passCards (cards_to_pass) {
if (this.#state != 'passing') return;

HU.positions.forEach(p => this.#hands[p].remove(cards_to_pass[p]));
HU.positions.forEach(p => {
let pass_to = HU.passing_maps[this.#passing][p];
this.#hands[pass_to].add(cards_to_pass[p]);
});

this.#state = 'playing';

let lead = HU.positions.find(p => this.#hands[p].contains(Card.TWO_OF_CLUBS));

this.#current_trick = new Trick(lead);

this.dispatchEvent(new Event('stateupdate'));
this.dispatchEvent(new Event('trickstart'));
};

playCardIntoTrick (position, card) {
if (this.#state != 'playing') return;

this.#hands[position].remove([card]);
this.#current_trick.playCard(card);
this.dispatchEvent(new CustomEvent('trickplay', {detail: {
position: position,
card: card
}}));

if (this.#current_trick.isComplete()) {
this.dispatchEvent(new Event('trickend'));
}
};

collectTrick (position) {
if (this.#state != 'playing') return;

this.#collected_tricks[position].push(this.#current_trick);
this.dispatchEvent(new CustomEvent('trickcollected', {detail: {
position: position,
trick: this.#current_trick}
}));
this.#current_trick = null;
};

setupTrick (position) {
if (this.#state != 'playing') return;

this.#current_trick = new Trick(position);
this.dispatchEvent(new Event('trickstart'));
};

updateScoreLog (scorelog_entry, moonshooter) {
this.#scorelog.push(scorelog_entry);
this.dispatchEvent(new CustomEvent('scoreupdate', {detail: {
entry: scorelog_entry,
moonshooter: moonshooter
}}));
};

setupGame (passing) {
if ((this.#state == 'complete') || (this.#state == 'passing')) return;

this.#passing = passing;
this.#hands = this.#dealCards();
this.#current_trick = null;
this.#collected_tricks = {
north: [],
east: [],
south: [],
west: []
};
this.#state = 'passing';

this.dispatchEvent(new Event('stateupdate'));
};

matchOver () {
this.#state = 'complete';
this.dispatchEvent(new Event('stateupdate'));
};

// These methods are available to view objects to query model information

getState () {
return this.#state;
}

getPlayerName (position) {
return this.#playerNames[position];
};

getCurrentGamePoints (position) {
return this.#collected_tricks[position].reduce((sum, trick) =>  sum + trick.getPoints(), 0);
};

getScoreLog () {
return this.#scorelog;
};

getScore (position) {
return this.#scorelog.reduce((score, entry) => score + entry[position], 0);
}

getPassing() {
return this.#passing;
}

getCurrentTrick () {
return this.#current_trick;
}

getTricksLeft () {
return 13 - HU.positions.reduce((sum, p) => sum += this.#collected_tricks[p].length, 0);
}

getHand (position) {
return this.#hands[position];
}

getCollectedTricks(position) {
return this.#collected_tricks[position];
}
}

export class Card {
#suit
#rank

static TWO_OF_CLUBS = new Card('clubs', 2);
static QUEEN_OF_SPADES = new Card('spades', 12);

constructor (suit, rank) {
this.#suit = suit;
this.#rank = rank;
}

toString() {
return `${this.getRankName()} of ${this.getSuit()}`;
}

getSuit() {
return this.#suit;
}

getRank() {
return this.#rank;
}

getRankName() {
let honors_map = {};
honors_map[11] = 'jack';
honors_map[12] = 'queen';
honors_map[13] = 'king';
honors_map[14] = 'ace';

return this.#rank < 11 ? this.#rank.toString() : honors_map[this.#rank];
}

equals(other) {
return (other.getRank() == this.#rank) && (other.getSuit() == this.#suit);
}
}

export class Trick {
#lead;
#next_to_play;
#played_by_position;

static #next_to_play_map = {
north: 'east',
east: 'south',
south: 'west',
west: 'north'
};

constructor(lead) {
this.#lead = lead;
this.#next_to_play = lead;
this.#played_by_position = {
north: null,
east: null,
south: null,
west: null
}
}

getLead() {
return this.#lead;
}

getLeadSuit() {
return this.getCard(this.getLead()).getSuit();
}

nextToPlay () {
return this.#next_to_play;
}

playCard (card) {
this.#played_by_position[this.#next_to_play] = card;
this.#next_to_play = this.isComplete() ? null : Trick.#next_to_play_map[this.#next_to_play];
}

getCard(position) {
return this.#played_by_position[position];
}

isComplete() {
return !HU.positions.find(p => this.#played_by_position[p] == null);
};

getPoints() {
return HU.positions.map(p => this.#played_by_position[p])
.filter(c => c != null)
.reduce((points, c) => points +
(c.equals(Card.QUEEN_OF_SPADES) ? 13 :
((c.getSuit() == 'hearts') ? 1 : 0)), 0);
}

toString() {
return `next to play: ${this.#next_to_play}
north: ${this.#played_by_position.north}
east : ${this.#played_by_position.east}
south: ${this.#played_by_position.south}
west : ${this.#played_by_position.west}
`
}
}

export class Hand extends EventTarget {
#cards;

constructor (cards) {
super();
this.#cards = cards;
}

contains (card) {
return this.#cards.some(c => c.equals(card));
}

hasSuit(suit) {
return this.#cards.some(c => c.getSuit() == suit);
}

hasOnlyHearts() {
return !this.#cards.some(c => c.getSuit() != 'hearts');
}

add (cards) {
if (cards.length == 0) return;

this.#cards.push(...cards);
this.dispatchEvent(new CustomEvent('update', {detail:
{type: 'add', cards: [...cards]}}));
}

remove (cards) {
if (cards.length == 0) return;

cards.forEach((c_to_remove) => {
this.#cards = this.#cards.filter((c) => !c.equals(c_to_remove));
});
this.dispatchEvent(new CustomEvent('update', {detail:
{type: 'remove', cards: [...cards]}}));
}

getCards () {
return [...this.#cards];
}

toString() {
return `Hearts  : ${this.#cards.filter(c=> c.getSuit() == 'hearts').sort((a,b) => a.getRank() - b.getRank()).map(c => c.getRankName()).join()}
Spades  : ${this.#cards.filter(c=> c.getSuit() == 'spades').sort((a,b) => a.getRank() - b.getRank()).map(c => c.getRankName()).join()}
Diamonds: ${this.#cards.filter(c=> c.getSuit() == 'diamonds').sort((a,b) => a.getRank() - b.getRank()).map(c =>  c.getRankName()).join()}
Clubs   : ${this.#cards.filter(c=> c.getSuit() == 'clubs').sort((a,b) => a.getRank() - b.getRank()).map(c => c.getRankName()).join()}
`;
}
}
< /code>
Вот контроллер: < /p>
import { HU } from "./hearts_utils.js";
import { Card } from "./hearts_model.js";

export class HeartsController {
#model;
#cards_to_pass;
#hearts_broken;

constructor(model) {
this.#model = model;
this.#model.addEventListener('trickend', () => this.#handleTrickEnd());
}

#doAsync() {
return new Promise((resolve) => setTimeout(resolve, 0));
}

startGame(north_name, east_name, south_name, west_name) {
this.#cards_to_pass = {
north: [],
east: [],
south: [],
west: []
};
this.#hearts_broken = false;
this.#doAsync().then(() => {
this.#model.initialize(north_name, east_name, south_name, west_name);
});
}

passCards(position, cards) {
if (this.#model.getState() != 'passing') {
alert('Controller error: attempt to pass cards when not in passing state');
return;
}

if (this.#model.getPassing() == 'none') {
alert('Controller error: attempt to pass cards when passing is none');
return;
}

if (cards.length != 3) {
alert('Controller error: attempt to pass more/less than three cards');
return;
}

let hand = this.#model.getHand(position);
if (cards.some(c => !hand.contains(c))) {
alert('Controller error: attempt to pass a card not in the hand of position');
return;
}

if (this.#cards_to_pass[position].length != 0) {
alert('Controller error: attempt to pass cards twice');
return;
}

this.#cards_to_pass[position] = [...cards];

if (!HU.positions.find(p => this.#cards_to_pass[p].length == 0)) {
this.#doAsync().then(() => {
this.#model.passCards(this.#cards_to_pass);
this.#cards_to_pass = {
north: [],
east: [],
south: [],
west: []
}
});
}
}

isPlayable(position, card) {
let cur_trick = this.#model.getCurrentTrick();
let hand = this.#model.getHand(position);

if (cur_trick.getLead() == position) {
// If lead of first trick in game, then only 2 of clubs is playable.
if (this.#model.getTricksLeft() == 13) {
return card.equals(Card.TWO_OF_CLUBS);
}

// Can only lead hearts if hearts are broken or hand only has hearts.
if (card.getSuit() == 'hearts') {
if (!this.#hearts_broken) {
return hand.hasOnlyHearts();
}
}
return true;
} else {
let lead_card = cur_trick.getCard(cur_trick.getLead());
if (!lead_card) {
return false;
}
if (!hand.hasSuit(lead_card.getSuit())) {
return true;
}
return card.getSuit() == lead_card.getSuit();
}
}

playCard(position, card) {
if (this.#model.getState() != 'playing') {
alert('Controller error: playCard called when not in playing state.');
return;
}

if (this.#model.getCurrentTrick().nextToPlay() != position) {
alert('Controller error: attempt to play card out of position');
return;
}

if (!this.#model.getHand(position).contains(card)) {
alert('Controller error: attmept to play card not in hand');
return;
}

if (!this.isPlayable(position, card)) {
alert('Controller error: attmept to play unplayable card');
return;
}

this.#doAsync().then(() => {
this.#model.playCardIntoTrick(position, card);
this.#hearts_broken ||= (card.getSuit() == 'hearts');
});
}

#handleTrickEnd() {
// Figure out who won.
let cur_trick = this.#model.getCurrentTrick();
let winner = cur_trick.getLead();
let winning_card = cur_trick.getCard(winner);

HU.positions.forEach(position => {
if (winner != position) {
let card = cur_trick.getCard(position);
if ((card.getSuit() == winning_card.getSuit()) &&
(card.getRank() >  winning_card.getRank())) {
winning_card = card;
winner = position;
}
}
});

this.#doAsync().then(() => this.#model.collectTrick(winner))
.then(() => {
if (this.#model.getTricksLeft() > 0) {
this.#model.setupTrick(winner);
return false;
} else {
// Game's over.
// Create scorelog entry (detect shooting the moon)
// Update scorelog
// Detect possible match end.
// Figure out next passing mode and set up next game.

//                let scorelog_entry = HU.positions.reduce((entry, pos) => entry[pos] = this.#model.getCurrentGamePoints(pos), {});
let scorelog_entry = {
north: this.#model.getCurrentGamePoints('north'),
east: this.#model.getCurrentGamePoints('east'),
south: this.#model.getCurrentGamePoints('south'),
west: this.#model.getCurrentGamePoints('west'),
};

let moonshooter = HU.positions.find(p => scorelog_entry[p] == 26);
if (moonshooter) {
HU.positions.forEach(p => {
scorelog_entry[p] = (scorelog_entry[p] + 26) % 52;
});
} else {
moonshooter = null;
}

this.#model.updateScoreLog(scorelog_entry, moonshooter);
return true;
}
})
.then((game_over) => {
if (game_over) {
if (HU.positions.find(p => this.#model.getScore(p) >= 100)) {
this.#model.matchOver();
} else {
let next_passing = HU.next_passing_map[this.#model.getPassing()];
this.#hearts_broken = false;
this.#cards_to_pass = {
north: [],
east: [],
south: [],
west: []
};
this.#model.setupGame(next_passing);

if (next_passing == 'none') {
this.#doAsync().then(() => this.#model.passCards(this.#cards_to_pass));
}
}
}
});
}
}
и вот представление (только файл, который может быть изменен)

Код: Выделить всё

import {HeartsRobotKmp} from "./hearts_robot_kmp.js";
import {Card, Hand, Trick} from "./hearts_model.js";
import {HU} from "./hearts_utils.js";

export class HeartsView {

#model
#controller

constructor(model, controller) {
this.#model = model;
this.#controller = controller;
this.dealPressed = false;
}

render(render_div) {
this.render_div = render_div;
render_div.innerHTML = '';

this.#model.addEventListener("stateupdate", () => this.update());
this.#model.addEventListener("trickplay", () => this.updateTrick());
this.#model.addEventListener("scoreupdate", () => this.updateScores());

if (this.#model.getState() === 'uninitialized') {
this.renderNameSetup();
}  else {
this.renderGameTable();
this.update();
}
}

renderNameSetup() {
this.render_div.innerHTML = `

Welcome to Hearts!
Your Name: 
West Player: 
North Player: 
East Player: 
Start Game

`;

document.getElementById('confirm-names').addEventListener('click', () =>  {
const south = document.getElementById('player-name').value || "You";
const west = document.getElementById('west-name').value;
const north = document.getElementById('north-name').value;
const east = document.getElementById('east-name').value;

this.#model.getPlayerName('north');
this.#model.getPlayerName('east');
this.#model.getPlayerName('south');
this.#model.getPlayerName('west');

this.#controller.startGame(north, east, south, west);
});
}

renderGameTable() {
const names = {
north: this.#model.getPlayerName('north'),
east: this.#model.getPlayerName('east'),
south: this.#model.getPlayerName('south'),
west: this.#model.getPlayerName('west')
}
this.render_div.innerHTML = `
'north north north'
'west center east'
'south south south';
gap: 20px; justify-items: center; align-items: center;">

${names.north}



${names.west}



Trick Goes Here
Deal
Pass
Scores Go Here

${names.east}


${names.south}



`;

document.getElementById("deal-button").addEventListener("click", () => {
this.dealPressed = true;
this.renderHands();

});

}

update() {
const state = this.#model.getState();
if (!document.getElementById("game-table")) {
this.renderGameTable();
}
if (state === 'passing') {
if (this.dealPressed) {
this.showPassCardsUI();
}
} else if (state === 'playing') {
this.showPlayableCards();
this.updateTrick();
} else if (state === 'complete') {
this.showWinner();
}
this.updateScores();
}

renderHands() {
const positions = ["north", "east", "south", "west"];

positions.forEach(position => {
const hand = this.#model.getHand(position); // A Hand object
if (!hand) return;

const cards = hand.getCards(); // Array of Card objects
const container = document.getElementById(`${position}-hand`);
container.innerHTML = '';

cards.forEach(card => {
const cardDiv = document.createElement("div");
cardDiv.className = "card";
cardDiv.textContent = `${card.getRankName()} of ${card.getSuit()}`;
container.appendChild(cardDiv);
});
});
}

showPassCardsUI() {
const southHandDiv = document.getElementById('south-hand');
const passButton = document.getElementById('pass-button');
passButton.style.display = 'inline-block';

const selectedCards = new Set();

// Make each card clickable
[...southHandDiv.children].forEach(cardDiv => {
cardDiv.addEventListener('click', () => {
const cardText = cardDiv.textContent;
cardDiv.classList.toggle('selected');

if (selectedCards.has(cardText)) {
selectedCards.delete(cardText);
} else if (selectedCards.size <  3) {
selectedCards.add(cardText);
}

passButton.disabled = selectedCards.size !== 3;
});
});

passButton.disabled = true;

passButton.addEventListener('click', () => {
// Convert text labels back to actual Card objects from model
const southHand = this.#model.getHand('south').getCards();
const selectedCardObjs = [...selectedCards].map(text =>
southHand.find(c => `${c.getRankName()} of ${c.getSuit()}` === text)
);

// Player passes cards
this.#controller.passCard("south", selectedCardObjs);

// Other players pass random cards
for (const pos of ["north", "east", "west"]) {
const hand = this.#model.getHand(pos).getCards();
const randomCards = this.getRandomCards(hand, 3);
this.#controller.passCard(pos, randomCards);
}

passButton.style.display = 'none'; // Hide after passing
});
}

getRandomCards(cards, count) {
const shuffled = [...cards].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}

showPlayableCards() { /* To be implemented */ }
updateTrick() { /* To be implemented */ }
updateScores() { /* To be implemented */ }
showWinner() { /* To be implemented */ }

renderMatchOver() {
// todo
}
}
Любая помощь будет оценена, большое спасибо!

Подробнее здесь: https://stackoverflow.com/questions/796 ... ockwise-to
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Javascript»