/*** * ==++== * * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ==--== * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * Table.cpp : Contains the main logic of game * * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ****/ #include "stdafx.h" #include "messagetypes.h" #include "Table.h" using namespace std; using namespace web; using namespace utility; using namespace web::http; using namespace web::http::experimental::listener; void DealerTable::Deal() { // // Give everyone two cards. // Players[0].Hand.revealBoth = false; m_currentPlayer = 0; m_betting = false; for (size_t i = 0; i < Players.size(); i++) { Players[i].Hand.Clear(); if ( i == 0 || Players[i].Hand.bet > 0 ) { Card card = m_shoe.front(); m_shoe.pop(); Players[i].Hand.cards.push_back(card); } } for (size_t i = 0; i < Players.size(); i++) { if ( i == 0 || Players[i].Hand.bet > 0 ) { Card card = m_shoe.front(); m_shoe.pop(); Players[i].Hand.AddCard(card); } } pplx::extensibility::scoped_critical_section_t lck(m_resplock); for (size_t player = m_currentPlayer+1; player < Players.size(); player++) { m_currentPlayer = (int)player; if ( Players[player].Hand.bet == 0 ) continue; if ( Players[player].Hand.state == HR_Active ) { m_responses[player]->reply(status_codes::OK, BJPutResponse(ST_YourTurn, this->AsJSON()).AsJSON()); m_responses[player].reset(); break; } } for (size_t i = 1; i < Players.size(); i++) { if ( i != m_currentPlayer ) { if ( m_responses[i] ) { m_responses[i]->reply(status_codes::OK, BJPutResponse(ST_Refresh, this->AsJSON()).AsJSON()); m_responses[i].reset(); } else { m_pendingrefresh[i] = ST_Refresh; } } } } void DealerTable::Hit(http_request message) { Card card = m_shoe.front(); m_shoe.pop(); Players[m_currentPlayer].Hand.AddCard(card); if ( Players[m_currentPlayer].Hand.state == HR_BlackJack || Players[m_currentPlayer].Hand.state == HR_Busted ) { Stay(message); } else { message.reply(status_codes::OK, BJPutResponse(ST_YourTurn, this->AsJSON()).AsJSON()); pplx::extensibility::scoped_critical_section_t lck(m_resplock); for (size_t i = 1; i < Players.size(); i++) { if ( i != m_currentPlayer ) { if ( m_responses[i] ) { m_responses[i]->reply(status_codes::OK, BJPutResponse(ST_Refresh, this->AsJSON()).AsJSON()); m_responses[i].reset(); } else { m_pendingrefresh[i] = ST_Refresh; } } } } } void DealerTable::DoubleDown(http_request message) { if ( m_currentPlayer == 0 || Players[m_currentPlayer].Hand.state != HR_Active ) { message.reply(status_codes::Forbidden, U("Not your turn")); return; } Player ¤t = Players[m_currentPlayer]; if ( current.Balance < current.Hand.bet ) { message.reply(status_codes::Forbidden, U("Not enough money")); return; } if ( current.Hand.cards.size() > 2 ) { message.reply(status_codes::Forbidden, U("Too many cards")); return; } // Double the bet current.Balance -= current.Hand.bet; current.Hand.bet *= 2; // Take one card and then stay Card card = m_shoe.front(); m_shoe.pop(); Players[m_currentPlayer].Hand.AddCard(card); Stay(message); } void DealerTable::Wait(http_request message) { utility::string_t name; auto query = http::uri::split_query(http::uri::decode(message.relative_uri().query())); auto itr = query.find(QUERY_NAME); if ( itr == query.end() ) { message.reply(status_codes::Forbidden, U("name and amount are required in query")); return; } else name = itr->second; int playerIdx = FindPlayer(name); if ( playerIdx > 0 ) { pplx::extensibility::scoped_critical_section_t lck(m_resplock); if ( m_pendingrefresh[playerIdx] != ST_None ) { message.reply(status_codes::OK, BJPutResponse(m_pendingrefresh[playerIdx], this->AsJSON()).AsJSON()); m_pendingrefresh[playerIdx] = ST_None; } else { m_responses[playerIdx] = message_wrapper(new http_request(message)); } } } void DealerTable::Bet(http_request message) { int amount; utility::string_t name; auto query = http::uri::split_query(http::uri::decode(message.relative_uri().query())); auto itrAmount = query.find(AMOUNT), itrName = query.find(QUERY_NAME); if ( itrAmount == query.end() || itrName == query.end() ) { message.reply(status_codes::Forbidden, U("name and amount are required in query")); return; } utility::istringstream_t ss(itrAmount->second); ss >> amount; name = itrName->second; int playerIdx = FindPlayer(name); if ( playerIdx > 0 ) { Players[playerIdx].Balance -= amount; Players[playerIdx].Hand.bet += amount; } m_betsMade += 1; m_responses[playerIdx] = message_wrapper(new http_request(message)); if ( m_betsMade == Players.size() - 1 ) { Deal(); } // It is **possible** that all players have Blackjack, in which case // the round is over. It's especially likely when there's only one // player at the table. if ( m_currentPlayer == Players.size() ) { m_currentPlayer = 0; DealerDeal(); } } void DealerTable::Insure(http_request message) { int amount; utility::string_t name; auto query = http::uri::split_query(http::uri::decode(message.relative_uri().query())); auto itrAmount = query.find(AMOUNT), itrName = query.find(QUERY_NAME); if ( itrAmount == query.end() || itrName == query.end() ) { message.reply(status_codes::Forbidden, U("name and amount are required in query")); return; } utility::istringstream_t ss(itrAmount->second); ss >> amount; name = itrName->second; int playerIdx = FindPlayer(name); if ( playerIdx > 0 ) { const BJHand &dealer = Players[0].Hand; if ( Players[playerIdx].Hand.insurance > 0.0 ) { message.reply(status_codes::Forbidden, U("Already insured")); return; } if ( dealer.cards.size() < 1 || dealer.revealBoth || dealer.cards[0].value != CV_Ace ) { message.reply(status_codes::Forbidden, U("Dealer is not showing an Ace")); return; } Players[playerIdx].Balance -= amount; Players[playerIdx].Hand.insurance += amount; } message.reply(status_codes::OK, BJPutResponse(ST_YourTurn, this->AsJSON()).AsJSON()); } void DealerTable::Stay(http_request message) { if ( m_currentPlayer == 0 ) return; if ( Players[m_currentPlayer].Hand.state == HR_Active ) Players[m_currentPlayer].Hand.state = HR_Held; //int idx = m_currentPlayer; message.reply(status_codes::OK, BJPutResponse(ST_Refresh, this->AsJSON()).AsJSON()); NextPlayer(message); } void DealerTable::NextPlayer(http_request message) { size_t player = m_currentPlayer+1; for (; player < Players.size(); player++) { pplx::extensibility::scoped_critical_section_t lck(m_resplock); if ( Players[player].Hand.bet > 0 && Players[player].Hand.state == HR_Active ) { m_responses[player]->reply(status_codes::OK, BJPutResponse(ST_YourTurn, this->AsJSON()).AsJSON()); m_responses[player].reset(); break; } } m_currentPlayer = (int)player; if ( m_currentPlayer == Players.size() ) m_currentPlayer = 0; if ( m_currentPlayer == 0 ) DealerDeal(); } void DealerTable::PayUp(size_t idx) { Player &player = Players[idx]; if ( player.Hand.result == HR_PlayerWin ) { player.Balance += player.Hand.bet*2; player.Hand.bet = 0.0; } else if ( player.Hand.result == HR_ComputerWin ) { player.Hand.bet = 0.0; } else if ( player.Hand.result == HR_PlayerBlackJack ) { player.Balance += player.Hand.bet*2.5; player.Hand.bet = 0.0; } else if ( player.Hand.result == HR_Push ) { player.Balance += player.Hand.bet; player.Hand.bet = 0.0; } // Handle insurance if ( player.Hand.insurance > 0 && Players[0].Hand.state == HR_PlayerBlackJack ) { player.Balance += player.Hand.insurance*3; } player.Hand.insurance = 0; pplx::extensibility::scoped_critical_section_t lck(m_resplock); if ( m_responses[idx] ) { m_responses[idx]->reply(status_codes::OK, BJPutResponse(ST_PlaceBet, this->AsJSON()).AsJSON()); m_responses[idx].reset(); } else { m_pendingrefresh[idx] = ST_PlaceBet; } } void DealerTable::DealerDeal() { BJHand &dealersHand = Players[0].Hand; dealersHand.revealBoth = true; NumericHandValues handValue = dealersHand.GetNumericValues(); while ( handValue.high < 17 || (handValue.high > 21 && handValue.low < 17) ) { Card card = m_shoe.front(); m_shoe.pop(); dealersHand.AddCard(card); if ( dealersHand.state == HR_BlackJack || Players[m_currentPlayer].Hand.state == HR_Busted ) break; handValue = dealersHand.GetNumericValues(); } for (size_t i = 1; i < Players.size(); i++) { Player &player = Players[i]; if ( player.Hand.bet == 0.0 ) { player.Hand.result = HR_None; continue; } if ( player.Hand.state == HR_Busted ) { player.Hand.result = HR_ComputerWin; continue; } if ( player.Hand.state == HR_BlackJack ) { player.Hand.result = (dealersHand.state == HR_BlackJack) ? HR_Push : HR_PlayerBlackJack; continue; } if ( dealersHand.state == HR_Busted ) { player.Hand.result = HR_PlayerWin; continue; } if ( dealersHand.state == HR_BlackJack ) { player.Hand.result = HR_ComputerWin; continue; } NumericHandValues value = player.Hand.GetNumericValues(); player.Hand.result = (value.Best() < handValue.Best()) ? HR_ComputerWin : ((value.Best() == handValue.Best()) ? HR_Push : HR_PlayerWin); } m_betting = true; for (size_t i = 1; i < Players.size(); i++) { PayUp(i); } m_betsMade = 0; } // TODO: Right now, players must make sure to get in when the dealer is taking bets. // Change the logic to anticipate that a new player may come to the table at // any point, and wait until the next round. bool DealerTable::AddPlayer(const Player &player) { pplx::extensibility::scoped_critical_section_t lck(m_resplock); int idx = FindPlayer(player.Name); if ( idx > 0 ) return false; Players.push_back(player); m_responses.push_back(message_wrapper()); m_pendingrefresh.push_back(ST_None); return true; } bool DealerTable::RemovePlayer(const utility::string_t &name) { pplx::extensibility::scoped_critical_section_t lck(m_resplock); auto evnts = m_responses.begin(); auto pends = m_pendingrefresh.begin(); for (auto iter = Players.begin(); iter != Players.end(); iter++, evnts++, pends++) { if ( iter->Name == name ) { Players.erase(iter); m_responses.erase(evnts); m_pendingrefresh.erase(pends); return true; } } return false; } void DealerTable::FillShoe(size_t decks) { // // Value == suit*16+(facevalue-1) // // // Stack the decks. // std::shared_ptr ss(new int[decks * 52]); auto shoe = ss.get(); for (size_t d = 0; d < decks ; d++) { for (int suit = 0; suit < 4; suit++) { for (int fv = 0; fv < 13; fv++) { shoe[d*52+suit*13+fv] = suit*16+(fv+1); } } } // Randomize the shoe #ifdef _WIN32 LARGE_INTEGER li; QueryPerformanceCounter(&li); mt19937 eng(li.LowPart); #else struct timeval val; gettimeofday(&val, nullptr); mt19937 eng(val.tv_usec); #endif uniform_int_distribution dist(0,(int)(decks*52-1)); #define ITER 4 for (size_t r = 0; r < ITER; r++) { for (size_t i = 0; i < decks*52; i++) { int other = dist(eng); swap(shoe[i],shoe[other]); } } // // Convert to the other format // for (size_t i = 0; i < decks*52; i++) { Card card; card.suit = (CardSuit)(shoe[i] / 16); card.value = (CardValue)(shoe[i] % 16); m_shoe.push(card); } m_stopAt = uniform_int_distribution(26,52)(eng); } int DealerTable::FindPlayer(const utility::string_t &name) { int idx = 0; for (auto iter = Players.begin(); iter != Players.end(); iter++, idx++) { if ( iter->Name == name ) { return idx; } } return -1; }