我正在尝试制作一个BlackJack游戏!我已经成功实现了玩家互动!然而,我决定提升难度,加入一些AI,这样我就可以与AI对战,进行一场真正的较量,可以这么说。
我的主要问题是,我已经坐在这儿思考了大约一个小时左右,考虑AI是如何工作的,以及我如何能使用它,但我还没有想出任何可行的方法。所以我想问问大家是否有任何建议,或者能指导我一个方向。
我还没有任何关于AI的代码,因为我不知道如何开始或如何操作AI。这就是为什么我希望能得到一些指导。
现在我将发布我认为所有相关的类。我没有包含Game类。Game类只是用于验证和检查卡牌以及所有那些神奇的东西。
Card类:
public class Card{private int rank, suit;private String[] suitNames = new String[]{ "H", "C", "S", "D" };private String[] rankNumber = new String[]{ "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };private int[] points = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10};Card(int suitIndex, int rankIndex){ rank = rankIndex; suit = suitIndex;}public @Override String toString(){ return rankNumber[rank]+suitNames[suit]; }public int getRank(){ return rank;}public int getSuit(){ return suit;}public String getSuitName(){ return suitNames[suit];}public String getRankName(){ return rankNumber[rank];}public int getPoints(){ return points[rank];}public ImageIcon ImageOfCard() throws Exception{ ImageIcon icon = new ImageIcon("/StandardDeck/GameCards/"+getRankName() + getSuitName()+".png"); return icon;}}
BlackJack类(包含所有功能的游戏类)是的,我确实使用了Java GUI来开发它。
public class BlackJack extends JFrame {Game game;Deck deck;Card cards;Player player;Dealer dealer;JLabel[] playerCardSlots;JLabel[] dealerCardSlots;public BlackJack() { String name = JOptionPane.showInputDialog(null, "Enter your name"); deck = new Deck(4); game = new Game(); player = new Player(name); dealer = new Dealer(deck); initComponents(); SetButtons(false); playerCardSlots = new JLabel[]{Player1Card1, Player1Card2, Player1Card3, Player1Card4, Player1Card5}; dealerCardSlots = new JLabel[]{DealerCard1, DealerCard2, DealerCard3, DealerCard4, DealerCard5};}@SuppressWarnings("unchecked")// <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { Player1Card1 = new javax.swing.JLabel(); Player1Card2 = new javax.swing.JLabel(); Player1Card3 = new javax.swing.JLabel(); Player1Card4 = new javax.swing.JLabel(); Player1Card5 = new javax.swing.JLabel(); Player1Name = new javax.swing.JLabel(); HitButton = new javax.swing.JButton(); StandButton = new javax.swing.JButton(); PointsLabel = new javax.swing.JLabel(); DealButton = new javax.swing.JButton(); DealerCard1 = new javax.swing.JLabel(); DealerCard2 = new javax.swing.JLabel(); DealerCard3 = new javax.swing.JLabel(); DealerCard4 = new javax.swing.JLabel(); DealerCard5 = new javax.swing.JLabel(); DealerPointsLabel = new javax.swing.JLabel(); DealerLabel = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); Player1Name.setText("Player 1 Name"); HitButton.setText("Hit"); HitButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { HitButtonActionPerformed(evt); } }); StandButton.setText("Stand"); StandButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { StandButtonActionPerformed(evt); } }); PointsLabel.setText("points"); DealButton.setText("Deal"); DealButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { DealButtonActionPerformed(evt); } }); DealerPointsLabel.setText("points"); DealerLabel.setText("Dealer"); org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .add(54, 54, 54) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .add(76, 76, 76) .add(Player1Name) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 104, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(DealerPointsLabel) .add(PointsLabel)) .add(128, 128, 128))) .add(DealButton)) .add(layout.createSequentialGroup() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup() .add(Player1Card1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(Player1Card2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(Player1Card3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(Player1Card4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(12, 12, 12) .add(Player1Card5, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) .add(DealerLabel) .add(layout.createSequentialGroup() .add(DealerCard1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(DealerCard2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(12, 12, 12) .add(DealerCard3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(DealerCard4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(DealerCard5, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false) .add(HitButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(StandButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 75, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .add(13, 13, 13) .add(DealerLabel) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) .add(HitButton) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(DealerCard1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(DealerCard2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(DealerCard3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(DealerCard4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(DealerCard5, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(StandButton) .add(DealerPointsLabel)) .add(4, 4, 4) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(PointsLabel) .add(DealButton)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(Player1Card1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(Player1Card2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(Player1Card3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(Player1Card4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(Player1Card5, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 60, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(Player1Name) .addContainerGap(29, Short.MAX_VALUE)) ); pack();}// </editor-fold> private void HitButtonActionPerformed(java.awt.event.ActionEvent evt) { PlayerHit();} private void StandButtonActionPerformed(java.awt.event.ActionEvent evt) { PlayerStand();} private void DealButtonActionPerformed(java.awt.event.ActionEvent evt) { ResetGame();} public void SetName(){ Player1Name.setText(player.getName());}public void DealCards(){ player.AddCard(deck.DrawCard()); dealer.AddCard(deck.DrawCard()); player.AddCard(deck.DrawCard()); dealer.AddCard(deck.DrawCard()); ShowCards(player, player.CountCards()); ShowCards(dealer, 1); SetButtons(true);}public void DealerHit(){ dealer.AddCard(deck.DrawCard());}public void DealerStand(){ dealer.PlayerStand();}public void PlayerHit(){ player.AddCard(deck.DrawCard()); ShowCards(player, player.CountCards());}public void PlayerStand(){ player.PlayerStand(); SetButtons(false); dealer.PlayerTurn();}public void ShowCards(Player person, int cards){ if(!person.getName().equals("Dealer")){ for(int i = 0; i < cards; i++){ playerCardSlots[i].setText(person.getCard(i).toString()); } int points = game.CardTotal(person); if(game.PlayerBust(points)){ PointsLabel.setText("BUST!"); SetButtons(false); } else{ PointsLabel.setText(points+""); } if(game.CardDraw(person)){ PointsLabel.setText("5 Card Draw!"); SetButtons(false); } } else{ for(int i = 0; i < cards; i++){ dealerCardSlots[i].setText(person.getCard(i).toString()); } int points = game.CardTotal(person, cards); if(game.PlayerBust(points)){ DealerPointsLabel.setText("BUST!"); SetButtons(false); } else{ DealerPointsLabel.setText(points+""); } if(game.CardDraw(person)){ DealerPointsLabel.setText("5 Card Draw!"); SetButtons(false); } if(points >= 17){ } }}public void SetButtons(boolean enabled){ HitButton.setEnabled(enabled); StandButton.setEnabled(enabled);}public void ResetGame(){ for(JLabel label : playerCardSlots){ label.setText(""); } for(JLabel label : dealerCardSlots){ label.setText(""); } player.ClearCards(); dealer.ClearCards(); deck = new Deck(4); DealCards(); SetName(); player.PlayerTurn(); dealer.PlayerStand();}// <editor-fold defaultstate="collapsed" desc="Variables">// Variables declaration - do not modify private javax.swing.JButton DealButton;private javax.swing.JLabel DealerCard1;private javax.swing.JLabel DealerCard2;private javax.swing.JLabel DealerCard3;private javax.swing.JLabel DealerCard4;private javax.swing.JLabel DealerCard5;private javax.swing.JLabel DealerLabel;private javax.swing.JLabel DealerPointsLabel;private javax.swing.JButton HitButton;private javax.swing.JLabel Player1Card1;private javax.swing.JLabel Player1Card2;private javax.swing.JLabel Player1Card3;private javax.swing.JLabel Player1Card4;private javax.swing.JLabel Player1Card5;private javax.swing.JLabel Player1Name;private javax.swing.JLabel PointsLabel;private javax.swing.JButton StandButton;// End of variables declaration //</editor-fold>}
Player类:
public class Player{ private String playerName; private ArrayList<Card> playerCards = new ArrayList<Card>(); private boolean turn = false;Player(String name){ playerName = name;}public Card getCard(int index){ return playerCards.get(index);}public void AddCard(Card card){ playerCards.add(card);}public void ClearCards(){ playerCards.clear();}public int CountCards(){ return playerCards.size();}public String getName(){ return playerName;}public void PlayerStand(){ turn = false;}public void PlayerTurn(){ turn = true;}public boolean getTurn(){ return turn;}}
回答:
首先:从极客的角度来看,Blackjack确实很有趣。
正如有人评论的那样:银行使用固定的算法来执行所有操作。这些操作(通常?还是总是?你自己去发现。)仅依赖于银行的牌。首先实现这个。
对于玩家,你绝对应该实现Blackjack玩家所说的“基本策略”。我猜谷歌搜索会给你很多结果。
现在这是我会研究的内容:
在实现了银行的固定策略和玩家的“基本策略”后,你可以开始进行蒙特卡洛模拟来找出游戏的期望值。尽量让MC模拟代码尽可能快,因为你可能想进行大量的MC模拟。
现在尝试稍微修改一下牌组。例如,尝试从牌组中移除所有的5。然后进行相同的MC模拟,看看这是否会改变期望值。你想玩没有5的BJ吗?
现在,再次修改牌组。尝试移除牌组中一半的10(即所有值为10的牌的一半)。这如何改变玩家的期望值?
现在再次修改牌组。尝试移除一半的非10值的牌。这如何影响期望值?
继续稍微修改牌组,尝试理解剩余牌分布中的微小偏差如何影响期望值。你应该利用这种理解来调整游戏的赌注。因此,策略不是要不要要牌/停止要牌的行动,而是基于鞋中剩余牌的分布偏差进行的资金管理。继续基于“基本策略”做出要牌/停止要牌的决定!
进一步的步骤:
也许你可以训练一个神经网络(或其他一些估计算法)来根据牌组中已知的剩余牌估计游戏的期望值?(你必须跟踪鞋中剩余的牌。)也许你甚至可以将MC模拟工具连接到训练算法中,为你提供一种强化学习算法。这实际上开始听起来像真正的极客乐趣!或者你可以让MC模拟器运行得足够快,以便即时得到期望值的良好答案。
更进一步的步骤:
当你找到一种好的方法来估计游戏的期望值,基于鞋中剩余的牌(和“基本策略”),并且速度足够快时,你可能能够为资金管理实现一个强化学习算法。
…或者你可以坚持简单的计划:正期望值 => 高赌注。负期望值 => 低赌注。
祝你研究顺利,
-Øystein