[#2] Пишем игру Backjack (Очко). Постановка задачи

Начальная постановка описана тут

Сам для себя поставил задачу сделать игру в 21(BlackJack) на js. То есть с начала должна составляться колода, после она перемешивается в разный порядок и создается картинка обратной стороны карты к каждому диву, должны выводится очки свои и соперника. После когда на обратную сторону карты в колоде нажимают срабатывает обработчик который вызывает функцию что сменяет класс, ссылку картинки карты и введёт счет, также сверяет счёт, что когда будет ровно 21 то выводит сообщение или когда больше 21 выводит другое сообщение. Когда игрок решит нажать пасс тогда сработает обработчик событий которой будет повешан таймаут каждые 3 секунды на другую функцию и если эта функция набрала ровно 21 то победил соперник и выводится сообщение, но если больше 21 то соперник проиграл. И также хотелось сделать раунды чтобы если кто- то победил очко засчитывалось одному из игроков.
Вот такую кашу заварил )

Сразу отмечу что в описании смешаны подходы к решению (детали реализации) и сама задача. Когда вы будете работать с клиентами или ProjectManager-ами и другими людьми которые вам будут ставить задачи, они почти всегда будут не в состоянии отделить задачу от деталей реализации или лишней информации. Ваш первый шаг - составить описание задачи и подтвердить с теми кто составил начальное описание что ваше описание соответствует их видению. Я так же понимаю, что социально, организационно и по другим причинам этот шаг не всегда происходит. Просто имейте в виду что отделять сигнал от шума вам придется в любом случае.

Мой подход к очищению постановки задачи от делатей реализации:

Реализуем игру blackjack. Играет пользователь против компьютера. Когда идет ход пользователя он может выбрать взять карту или закончить ход. При руке 21 пользователь видит сообщение. При руке более 21 пользователь видет другое сообщение. Когда пользователь пасует, ход передается компьютеру. Когда идет ход компьютера программа по некому алгоритму решает брать игру или заканчивать ход, программа стремится набрать 21 или максимальное число очков при учете вероятностей. Ход компьютера виден пользователю. Когда ход компьютера заканчивается определяется кто выиграл. Ведется общий счет выигрышей-проигрышей. При сбросе все (общий счет, перемешанная колода) начинается заново.

Второй этап это уточнение. Редко когда постановка полная и содержит достаточно информации или вся информация непротиворечива. Читая постановку задачи вы представляете себе конечное решение и перебираете возможные случаи и граничные случаи (чем больше опыта тем этот процесс будет более легким и быстрым), и у вас будут возникать уточняющие вопросы. Из нужно задать-уточнить у постановщика задачи. И тут снова социальные, иерархические, организационные и другие аспекты типа лени могут не дать вам этого сделать. Тогда ответ на вопрос нужно будет придумать самому. Если вы знаете доменную область, то некоторы вопросы не возникнут. Я, например, игру не знаю, поэтому буду уточнять все что не понимаю.

На этом этапе вы наверняка не сможете задать все уточняющие вопросы что не есть критично, а есть данность с которой мы все работаем. Вы будете все лучше и лучше справляться с этим этапом уточнений по ходу получения опыта при выполнении этого шага из задачи в задачу.

Еще интересный аспект уточнений. В зависимости от ответа постановщика задачи решение может расти в сложности и времязатратах. Учитывайте это когда спрашивайте и предлагайте оптимизацию которая устроит постановщийка задачи. Конечно же, в моей практике они всегда ходят самое точное и самое лучшее решение за самые малые ресурсы, но так не бывает и нужны компромиссы. Свой комментарий о влиянии ответа я пишу италиком, решение за @NONLUCIFER как за постановщиком задачи.

Подскажи @NONLUCIFER:

  1. В каждом раунде всегда ходит первым человек и вторым программа? (решение будет проще если человек ходит всегда первым).
    Ответ: В каждом раунде ходит первый человек
  2. Всегда ли раунд заканчивается ходом компьютера? Например когда человек набрал 21 нужно ли запускать ход компьютера или нужно прекращать ход? (решение будет проще если раунды не прерываются)
    Ответ: Раунд не всегда будет заканчиваться ходом компьютера, то есть если человек набрал 21 то он выиграл, если человек нажал пасс, значит ход переходит к компьютеру и победа человека зависит от того какую руку набрал компьютер ровно 21 или меньше или больше
  3. Первый раунд начинается с полной колоды. Второй и последущие раунды используют полную колоду или то что осталось после предыдущего раунда? (решение будет сложнее если колода сохраняется между ходами)
    Ответ: Первый раунд начинается с полной колоды, последующие раунды тоже с полной колоды
  4. Если второй и последующие раунды используют остаток колоды то что делать когда на ходе колода закончилась?
    Ответ: Колода всегда будет обновляться в каждом раунде и будет полная
  5. Есть куча вопросов про логику хода компьютера, но мы пока отложим их потому что они инкапсулированы поэтому могут[quote=“dmitry, post:1, topic:3188, full:true”]
    Начальная постановка описана тут

Сам для себя поставил задачу сделать игру в 21(BlackJack) на js. То есть с начала должна составляться колода, после она перемешивается в разный порядок и создается картинка обратной стороны карты к каждому диву, должны выводится очки свои и соперника. После когда на обратную сторону карты в колоде нажимают срабатывает обработчик который вызывает функцию что сменяет класс, ссылку картинки карты и введёт счет, также сверяет счёт, что когда будет ровно 21 то выводит сообщение или когда больше 21 выводит другое сообщение. Когда игрок решит нажать пасс тогда сработает обработчик событий которой будет повешан таймаут каждые 3 секунды на другую функцию и если эта функция набрала ровно 21 то победил соперник и выводится сообщение, но если больше 21 то соперник проиграл. И также хотелось сделать раунды чтобы если кто- то победил очко засчитывалось одному из игроков.
Вот такую кашу заварил )

Сразу отмечу что в описании смешаны подходы к решению (детали реализации) и сама задача. Когда вы будете работать с клиентами или ProjectManager-ами и другими людьми которые вам будут ставить задачи, они почти всегда будут не в состоянии отделить задачу от деталей реализации или лишней информации. Ваш первый шаг - составить описание задачи и подтвердить с теми кто составил начальное описание что ваше описание соответствует их видению. Я так же понимаю, что социально, организационно и по другим причинам этот шаг не всегда происходит. Просто имейте в виду что отделять сигнал от шума вам придется в любом случае.

Мой подход к очищению постановки задачи от делатей реализации:

Реализуем игру blackjack. Играет пользователь против компьютера. Когда идет ход пользователя он может выбрать взять карту или закончить ход. При руке 21 пользователь видит сообщение. При руке более 21 пользователь видет другое сообщение. Когда пользователь пасует, ход передается компьютеру. Когда идет ход компьютера программа по некому алгоритму решает брать игру или заканчивать ход, программа стремится набрать 21 или максимальное число очков при учете вероятностей. Ход компьютера виден пользователю. Когда ход компьютера заканчивается определяется кто выиграл. Ведется общий счет выигрышей-проигрышей. При сбросе все (общий счет, перемешанная колода) начинается заново.

Второй этап это уточнение. Редко когда постановка полная и содержит достаточно информации или вся информация непротиворечива. Читая постановку задачи вы представляете себе конечное решение и перебираете возможные случаи и граничные случаи (чем больше опыта тем этот процесс будет более легким и быстрым), и у вас будут возникать уточняющие вопросы. Из нужно задать-уточнить у постановщика задачи. И тут снова социальные, иерархические, организационные и другие аспекты типа лени могут не дать вам этого сделать. Тогда ответ на вопрос нужно будет придумать самому. Если вы знаете доменную область, то некоторы вопросы не возникнут. Я, например, игру не знаю, поэтому буду уточнять все что не понимаю.

На этом этапе вы наверняка не сможете задать все уточняющие вопросы что не есть критично, а есть данность с которой мы все работаем. Вы будете все лучше и лучше справляться с этим этапом уточнений по ходу получения опыта при выполнении этого шага из задачи в задачу.

Еще интересный аспект уточнений. В зависимости от ответа постановщика задачи решение может расти в сложности и времязатратах. Учитывайте это когда спрашивайте и предлагайте оптимизацию которая устроит постановщийка задачи. Конечно же, в моей практике они всегда ходят самое точное и самое лучшее решение за самые малые ресурсы, но так не бывает и нужны компромиссы. Свой комментарий о влиянии ответа я пишу италиком, решение за @NONLUCIFER как за постановщиком задачи.

Подскажи @NONLUCIFER:

  1. В каждом раунде всегда ходит первым человек и вторым программа? (решение будет проще если человек ходит всегда первым).
  2. Всегда ли раунд заканчивается ходом компьютера? Например когда человек набрал 21 нужно ли запускать ход компьютера или нужно прекращать ход? (решение будет проще если раунды не прерываются)
  3. Первый раунд начинается с полной колоды. Второй и последущие раунды используют полную колоду или то что осталось после предыдущего раунда? (решение будет сложнее если колода сохраняется между ходами)
  4. Если второй и последующие раунды используют остаток колоды то что делать когда на ходе колода закончилась?
  5. Есть куча вопросов про логику хода компьютера, но мы пока отложим их потому что они инкапсулированы поэтому могут быть добавлены по ходу программы. Мы сделаем простейшую логику с минимум учетов факторов.

UPD

Чтобы было проще задавать вопросы я сделал скетч (прилагается ниже) и представлял в голове как будет работать программа. Я поленился лезть читать правила игры в интернете и задал возникшие вопросы напрямую.

В зависимости от ответов на вопросы скетч может меняться. А так же на практике у вас уже может быть скетч или дизайн на руках. И очень часто, в зависимости от уровня дизайнера и их желания работать, дизайн может быть недостаточным. Например не давать достаточно информации как выглядит то или иное состояние программы (начальное, конечное). Всегда на практике добивайтесь ясности на ранних этапах разработки решения. Чем позже станет понятно что чего-то не хватает, что-то не учтено что-то нужно переделать, тем сложнее будет внести изменение в код.


[/quote]

быть добавлены по ходу программы. Мы сделаем простейшую логику с минимум учетов факторов.


UPD

Чтобы было проще задавать вопросы я сделал скетч (прилагается ниже) и представлял в голове как будет работать программа. Я поленился лезть читать правила игры в интернете и задал возникшие вопросы напрямую.

В зависимости от ответов на вопросы скетч может меняться. А так же на практике у вас уже может быть скетч или дизайн на руках. И очень часто, в зависимости от уровня дизайнера и их желания работать, дизайн может быть недостаточным. Например не давать достаточно информации как выглядит то или иное состояние программы (начальное, конечное). Всегда на практике добивайтесь ясности на ранних этапах разработки решения. Чем позже станет понятно что чего-то не хватает, что-то не учтено что-то нужно переделать, тем сложнее будет внести изменение в код.


UPD2 @NONLUCIFER ответил (я добавил ответы к вопросам) и приложил изображение интерфейса. Теперь дело за дизайном доменной модели (данных и классов).

Думаю это идеальный случай для реакта

1 лайк

@dmitry

  1. В каждом раунде всегда ходит первым человек и вторым программа? (решение будет проще если человек ходит всегда первым).
    В каждом раунде ходит первый человек
  2. Всегда ли раунд заканчивается ходом компьютера? Например когда человек набрал 21 нужно ли запускать ход компьютера или нужно прекращать ход? (решение будет проще если раунды не прерываются)
    Раунд не всегда будет заканчиваться ходом компьютера, то есть если человек набрал 21 то он выиграл, если человек нажал пасс, значит ход переходит к компьютеру и победа человека зависит от того какую руку набрал компьютер ровно 21 или меньше или больше
  3. Первый раунд начинается с полной колоды. Второй и последущие раунды используют полную колоду или то что осталось после предыдущего раунда? (решение будет сложнее если колода сохраняется между ходами)
    Первый раунд начинается с полной колоды, последующие раунды тоже с полной колоды
  4. Если второй и последующие раунды используют остаток колоды то что делать когда на ходе колода закончилась?
    Колода всегда будет обновляться в каждом раунде и будет полная
1 лайк

Следующие шаги реализации:

@dmitry
Добавил ещё в код несколько функций которые подгружают при нажатии на колоду карт, карту в развернутом виде, но правильно ли хранить много картинок с разными названиями в папке проекта это же много занимает места, но по другому как не знаю . Можно было бы сделать на одной картинке все карты и когда человек нажимает на колоду и идёт проверка , например будет имя карты : туз, масть: бубна, тогда когда в условии увидит name == бубна тогда на картинке будет значение которое выделит именно эту карту.
Сейчас в коде добавил счёт и карты показываются.

class BlackJackGame {
    constructor(rootNode) {
        this.playercards = null;
        this.computercards = null;
        this.playerScore = null;
        this.computerScore = null;
        this.deck = null;
        this.DOMComputerScore = null;
        this.DOMPlayerScore = null;
        this.DOMComputerCards = null;
        this.DOMPlayersCards = null;
        this.DOMDeckofCards = null;
        this.DOMPassButton = null;
        this.DOMRootNode = null;

        this.DOMRootNode = rootNode;
        this.init();
    }

    init() {
        this.playercards = [];
        this.computercards = [];
        this.playerScore = [];
        this.computerScore = [];
        this.deck = this._createDeck();

        this.DOMRootNode.innerHTML = `
            <div class='computer-score'></div>
            <div class='player-score'></div>
            <div class='deck-of-cards'></div>
            <div class='computer-cards'></div>
            <div class='player-cards'></div>
            <button class='pass'>Pass</button>
        `
        this.DOMComputerScore = this.DOMRootNode.querySelector('.computer-score');
        this.DOMPlayerScore = this.DOMRootNode.querySelector('.player-score');
        this.DOMComputerCards = this.DOMRootNode.querySelector('.computer-cards');
        this.DOMPlayersCards = this.DOMRootNode.querySelector('.player-cards');
        this.DOMDeckofCards = this.DOMRootNode.querySelector('.deck-of-cards ');
        this.DOMPassButton = this.DOMRootNode.querySelector('.pass');

        this.DOMDeckofCards.addEventListener('click', () => {
            this.pickPlayerCard();
        });
    }

    sumOfPlayerScore() {
        var s = 0;
        for (var i = 0; i < this.playerScore.length; i++) {
            s += this.playerScore[i];
        }
        return s
    }

    pickPlayerCard() {
        const card = this.deck.pop();
        this.playerScore.push(card.weight);
        const score = this.sumOfPlayerScore();
        this.playercards.push(card);
        this.renderPlayerHand(card, score);
        this.checkPlayerScore(score);
    }

    renderPlayerHand(card, score) {
        this.DOMPlayersCards.innerHTML = this.DOMPlayersCards.innerHTML +`
            <div class=player-card style=background-image:url(./img/`+card.name +`-`+card.suit +`.jpg)></div>
        `
        this.DOMPlayerScore.innerHTML = `
            ${JSON.stringify(score)}
        `
    }

    checkPlayerScore(score){
        if(score === 21)alert('You win');
        else if(score > 21)alert('You lose') 
    }

    _createDeck() {
        const suits = ['hearts', 'clubs', 'diamonds', 'spades'];
        const cards = [{ name: 'tuz', weight: 11 }, { name: 'korol', weight: 4 }, { name: 'dama', weight: 3 }, { name: 'valet', weight: 2 }, { name: '10', weight: 10 }, { name: '9', weight: 9 }, { name: '8', weight: 8 }, { name: '7', weight: 7 }, { name: '6', weight: 6 }];
        const deck = [];
        for (let suit of suits) {
            for (let cardInfo of cards) {
                deck.push({
                    suit: suit,
                    name: cardInfo.name,
                    weight: cardInfo.weight
                })
            }
        }
        deck.sort(() => {
            return Math.random() > 0.5 ? 1 : -1;
        })

        return deck;
    }
}

new BlackJackGame(document.querySelector('.game'));

Есть вопрос я в функции которые добавил передаю параметры, лучше будет сделать переменную глобальной или локальной и передавать в параметры функции ?

1 лайк

Для читающих. @NONLUCIFER дополняет код который был описан-написан в теме [#4] Пишем игру Backjack (Очко). Колода и ход игрока


Я понимаю вопрос. Я смотрю на него как “целесообразно/нецелесообразно”. Цели сделать самую “легкую” игру у нас нет, а куча файлов с разными именами удобна для работы с ними, именно как ты и сделал с использованием уже существующих данных. Минусов подхода не нашел. Так что подход с кучей файлов целесообразен.

Вижу добавлено

this.renderPlayerHand(card, score);

card тут лишняя. Мы при любом вызове renderPlayerHand будем заново отрисовывать поле, а данные брать из this.playerHand.

Со score вопрос не так однозначен.

Я люблю когда данные явно передаются в функцию потому что это делает отладку простой. Но мы уже начали писать в стиле когда данные берутся из this.X. Плюс этот счет нужен в нескольких местах. Каждый раз прописывать его вычислением с помощью функции так себе идея. Ради однообразия все что можно взять из this.X я бы использовал геттер (документация). Не уверен подход он верен для для начинающих джаваскриптеров, хотя просто отлично подходит под нашу конструкцию. Давай сделаем новый геттер войства playerScore в который прописать всю логику вычисления очков. Его нужно описать до конструктора чтобы читающему было понятно с чем имеется дело. Использование будет выглядеть как просто обращение к свойству. Вопрос производительности вообще не стоит потому что простота кода в данном случае более целесообразна чем быстрота выполнения кода.