http://en.doc.boardgamearena.com/api.php?action=feedcontributions&user=JonChambers&feedformat=atom
Board Game Arena - User contributions [en]
2024-03-28T21:21:01Z
User contributions
MediaWiki 1.39.0
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9785
Gamehelpladyschoice
2021-10-18T04:43:09Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol><p>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.</p><br />
<ul>Gentlemen choose to either BOAST or CHANGE on their turn.</ul><br />
<ul>The Catch chooses to either COURT or MARRY on her turn.</ul></ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
<p>Pass The Catch card to the left. Start next round.</p><br />
<h2>1. Set up Phase</h2><br />
<p>Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.</p><br />
<p>Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.</p><br />
<h2>2. Romance Phase</h2><br />
<p>Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.</p><br />
<h3>On a Gentleman’s Turn:</h3><br />
<p>Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)</p><br />
<p>On your turn, select one, and only one, of the following actions:</p><br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
<p>You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)</p><br />
<h3>On The Catch’s Turn:</h3><br />
<p>On your turn, choose a gentleman. Then choose one, and only one, of the following actions:</p><br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
<p>Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry.</p><br />
<p>If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing.</p><br />
<p>If the remaining Catch is The First Catch, she must choose immediately without the extra action.</p></blockquote><br />
<p>Once all Catches have chosen, go straight to ranking phase.<p><br />
<h2>Biological Clock</h2><br />
<p>Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)</p><br />
<p>Now that it is revealed, The Catch can only choose the Marry action.</p><br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
<p>All gentlemen reveal their cards:</p><br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
<p>Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.</p><br />
<p>The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)</p><br />
<h2>4. Scoring Phase</h2><br />
<p>The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him.</p><br />
<p>Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.</p><br />
<p>If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.</p><br />
<p>If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.</p><br />
<p>The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)</p><br />
<p>Unmarried gentlemen get nothing.</p><br />
<p>The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
<p>Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.</p><br />
<h2>Game End</h2><br />
<p>The player who has received the most total Love and True Love cards wins.</p><br />
<p>If two players have the same number of Love cards, the player with the most True Love wins the tie.</p><br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
<p>Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)</p><br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
<p>Choose a gentleman to MARRY.</p><br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9784
Gamehelpladyschoice
2021-10-18T03:04:20Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol><p>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.</p><br />
<ul>Gentlemen choose to either BOAST or CHANGE on their turn.</ul><br />
<ul>The Catch chooses to either COURT or MARRY on her turn.</ul></ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
<p>Pass The Catch card to the left. Start next round.</p><br />
<h2>1. Set up Phase</h2><br />
<p>Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.</p><br />
<p>Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.</p><br />
<h2>2. Romance Phase</h2><br />
<p>Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.</p><br />
<h3>On a Gentleman’s Turn:</h3><br />
<p>Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)</p><br />
<p>On your turn, select one, and only one, of the following actions:</p><br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
<p>You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)</p><br />
<h3>On The Catch’s Turn:</h3><br />
<p>On your turn, choose a gentleman. Then choose one, and only one, of the following actions:</p><br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
<p>Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry.</p><br />
<p>If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing.</p><br />
<p>If the remaining Catch is The First Catch, she must choose immediately without the extra action.</p></blockquote><br />
<p>Once all Catches have chosen, go straight to ranking phase.<p><br />
<h2>Biological Clock</h2><br />
<p>Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)</p><br />
<p>Now that it is revealed, The Catch can only choose the Marry action.</p><br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
<p>All gentlemen reveal their cards:</p><br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
<p>Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.</p><br />
<p>The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)</p><br />
<h2>4. Scoring Phase</h2><br />
<p>The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him.</p><br />
<p>Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.</p><br />
<p>If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.</p><br />
<p>If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.</p><br />
<p>The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)</p><br />
<p>Unmarried gentlemen get nothing.</p><br />
<h3>Example</h3><br />
<p>It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card.</p><br />
<p>The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.</p><br />
<p>Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).</p><br />
<p>All gentlemen reveal.</p><br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9783
Gamehelpladyschoice
2021-10-18T02:59:51Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol><p>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.</p><br />
<ul>Gentlemen choose to either BOAST or CHANGE on their turn.</ul><br />
<ul>The Catch chooses to either COURT or MARRY on her turn.</ul></ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
<p>Pass The Catch card to the left. Start next round.</p><br />
<h2>1. Set up Phase</h2><br />
<p>Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.</p><br />
<p>Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.</p><br />
<h2>2. Romance Phase</h2><br />
<p>Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.</p><br />
<h3>On a Gentleman’s Turn:</h3><br />
<p>Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)</p><br />
<p>On your turn, select one, and only one, of the following actions:</p><br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
<p>You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)</p><br />
<h3>On The Catch’s Turn:</h3><br />
<p>On your turn, choose a gentleman. Then choose one, and only one, of the following actions:</p><br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
<p>Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry.</p><br />
<p>If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing.</p><br />
<p>If the remaining Catch is The First Catch, she must choose immediately without the extra action.</p></blockquote><br />
<p>Once all Catches have chosen, go straight to ranking phase.<p><br />
<h2>Biological Clock</h2><br />
<p>Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)</p><br />
<p>Now that it is revealed, The Catch can only choose the Marry action.</p><br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
<p>All gentlemen reveal their cards:</p><br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
<p>Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.</p><br />
<p>The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)</p><br />
<h2>4. Scoring Phase</h2><br />
<p>The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him.</p><br />
<p>Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.</p><br />
<p>If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.</p><br />
<p>If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.</p><br />
<p>The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)</p><br />
<p>Unmarried gentlemen get nothing.</p><br />
<h3>Example</h3><br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9782
Gamehelpladyschoice
2021-10-18T02:53:01Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol><p>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.</p><br />
<ul>Gentlemen choose to either BOAST or CHANGE on their turn.</ul><br />
<ul>The Catch chooses to either COURT or MARRY on her turn.</ul></ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
<p>Pass The Catch card to the left. Start next round.</p><br />
<h2>1. Set up Phase</h2><br />
<p>Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.</p><br />
<p>Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.</p><br />
<h2>2. Romance Phase</h2><br />
<p>Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.</p><br />
<h3>On a Gentleman’s Turn:</h3><br />
<p>Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)</p><br />
<p>On your turn, select one, and only one, of the following actions:</p><br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
<p>You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)</p><br />
<h3>On The Catch’s Turn:</h3><br />
<p>On your turn, choose a gentleman. Then choose one, and only one, of the following actions:</p><br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
<p>Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry.</p><br />
<p>If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing.</p><br />
<p>If the remaining Catch is The First Catch, she must choose immediately without the extra action.</p></blockquote><br />
<p>Once all Catches have chosen, go straight to ranking phase.<p><br />
<h2>Biological Clock</h2><br />
<p>Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)</p><br />
<p>Now that it is revealed, The Catch can only choose the Marry action.</p><br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9781
Gamehelpladyschoice
2021-10-18T02:50:00Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol><p>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.</p><br />
<ul>Gentlemen choose to either BOAST or CHANGE on their turn.</ul><br />
<ul>The Catch chooses to either COURT or MARRY on her turn.</ul></ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:</h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
<h3>On The Catch’s Turn:</h3><br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9780
Gamehelpladyschoice
2021-10-18T02:49:05Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol><p>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.</p><br />
<p>Gentlemen choose to either BOAST or CHANGE on their turn.</p><br />
<p>The Catch chooses to either COURT or MARRY on her turn.</p></ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:</h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
<h3>On The Catch’s Turn:</h3><br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9779
Gamehelpladyschoice
2021-10-18T02:47:51Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
<p>The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.</p><br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
<p>Each player is given Love cards according to the player count:</p><br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
<p>These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.</p><br />
<p>Place a supply of True Love cards within reach.</p><br />
<p>Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.</p><br />
<p>Choose someone to be The Catch for the first round, and receive The Catch Player Card.</p><br />
<blockquote><h3>6 Players</h3><br />
<p>Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.</p><br />
<p>All other players play as gentlemen and are each given a Gentleman Player Card.</p></blockquote><br />
<h2>Round Overview</h2><br />
<p>Everyone gets to be The Catch for one round. Each round is made up of the following phases:</p><br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:</h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
<h3>On The Catch’s Turn:</h3><br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9778
Gamehelpladyschoice
2021-10-18T02:44:09Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:</h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
<h3>On The Catch’s Turn:</h3><br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
<ul>Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)</ul><br />
<ul>If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.</ul><br />
<ul>Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.</ul><br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9777
Gamehelpladyschoice
2021-10-18T02:41:49Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:</h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
<h3>On The Catch’s Turn:</h3><br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
<ul>COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.</ul><br />
<ul>MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.)</ul><br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9776
Gamehelpladyschoice
2021-10-18T02:40:41Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:</h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9775
Gamehelpladyschoice
2021-10-18T02:40:14Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
<h3>On a Gentleman’s Turn:<h3><br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
<ul>BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.</ul><br />
<ul>CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards.</ul><br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9774
Gamehelpladyschoice
2021-10-18T02:37:56Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [https://choicegames.com.au www.choicegames.com.au]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9773
Gamehelpladyschoice
2021-10-18T02:34:11Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [[www.choicegames.com.au]]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
<ol>1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.</ol><br />
<ol>2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.</ol><br />
<ol>3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.</ol><br />
<ol>4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.</ol><br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9772
Gamehelpladyschoice
2021-10-18T02:31:37Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [[www.choicegames.com.au]]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.<br />
2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.<br />
3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.<br />
4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.<br />
Pass The Catch card to the left. Start next round.<br />
<h2>1. Set up Phase</h2><br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<h2>2. Romance Phase</h2><br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
<h2>3. Ranking Phase</h2><br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
<h2>4. Scoring Phase</h2><br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9771
Gamehelpladyschoice
2021-10-18T02:29:12Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [[www.choicegames.com.au]]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h3>6 Players</h3>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h3>6 Players</h3><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.<br />
2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.<br />
3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.<br />
4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.<br />
Pass The Catch card to the left. Start next round.<br />
1. Set up Phase<br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<br />
2. Romance Phase<br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h3>6 Players</h3><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h3>6 Players</h3><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
3. Ranking Phase<br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
4. Scoring Phase<br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h3>6 Players</h3><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h3>6 Players</h3><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9770
Gamehelpladyschoice
2021-10-18T02:26:22Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [[www.choicegames.com.au]]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h2>6 Players</h2>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
<h2>Setting up the Game</h2><br />
Each player is given Love cards according to the player count:<br />
<ul>3 players, each get 2 Love cards</ul><br />
<ul>4 players, each get 3 Love cards</ul><br />
<ul>5 or 6 players, each get 4 Love cards</ul><br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<h2>Using Poker Chips</h2><br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h2>6 Players</h2><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
<h2>Round Overview</h2><br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.<br />
2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.<br />
3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.<br />
4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.<br />
Pass The Catch card to the left. Start next round.<br />
1. Set up Phase<br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<br />
2. Romance Phase<br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h2>6 Players</h2><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h2>6 Players</h2><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
3. Ranking Phase<br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
4. Scoring Phase<br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h2>6 Players</h2><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h2>6 Players</h2><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
<h2>Objective</h2><br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
<h2>Setup</h2><br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
<h2>Gameplay</h2><br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
<h2>COURT Action</h2><br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
<h2>MARRY</h2><br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9769
Gamehelpladyschoice
2021-10-18T02:06:30Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [[www.choicegames.com.au]]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
<h2>Objective</h2><br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
<blockquote><h2>6 Players</h2>If you are not playing with 6 players, you can skip the 6 Players blocks. A 6 player game has a few extra rules.</blockquote><br />
Setting up the Game<br />
Each player is given Love cards according to the player count:<br />
3 players, each get 2 Love cards<br />
4 players, each get 3 Love cards<br />
5 or 6 players, each get 4 Love cards<br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<br />
Using Poker Chips<br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
<blockquote><h2>6 Players</h2><br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card.</blockquote><br />
Round Overview<br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.<br />
2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.<br />
3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.<br />
4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.<br />
Pass The Catch card to the left. Start next round.<br />
1. Set up Phase<br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<br />
2. Romance Phase<br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
<blockquote><h2>6 Players</h2><br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.</blockquote><br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
<blockquote><h2>6 Players</h2><br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action.</blockquote><br />
3. Ranking Phase<br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
4. Scoring Phase<br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
<blockquote><h2>6 Players</h2><br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.</blockquote><br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
<blockquote><h2>6 Players</h2><br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”</blockquote><br />
<h1>2 Player Game</h1><br />
Objective<br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
Setup<br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
Gameplay<br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
COURT Action<br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
MARRY<br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9768
Gamehelpladyschoice
2021-10-18T01:53:52Z
<p>JonChambers: </p>
<hr />
<div><h3>To purchase a copy, visit: [[www.choicegames.com.au]]</h3><br />
Cards Used:<br />
<ul>70 Gentleman Attributes</ul><br />
<ul>The Catch</ul><br />
<ul>The Second Catch</ul><br />
<ul>4 Gentleman Player Cards</ul><br />
<ul>Biological Clock</ul><br />
<ul>24 Love</ul><br />
<ul>6 True Love</ul><br />
<ul>4 Ranks: Dandiest to Least Dandy</ul><br />
<h1>3-6 Player Game</h1><br />
Objective<br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
6 Players<br />
If you are not playing with 6 players, you can skip the 6 Players boxes. A 6 player game has a few extra rules.<br />
Setting up the Game<br />
Each player is given Love cards according to the player count:<br />
3 players, each get 2 Love cards<br />
4 players, each get 3 Love cards<br />
5 or 6 players, each get 4 Love cards<br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<br />
Using Poker Chips<br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
6 Players<br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card. <br />
Round Overview<br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.<br />
2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.<br />
3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.<br />
4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.<br />
Pass The Catch card to the left. Start next round.<br />
1. Set up Phase<br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<br />
2. Romance Phase<br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
6 Players<br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.<br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
6 Players<br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action. <br />
3. Ranking Phase<br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
4. Scoring Phase<br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
6 Players<br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.<br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
6 Players<br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”<br />
<h1>2 Player Game</h1><br />
Objective<br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
Setup<br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
Gameplay<br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
COURT Action<br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
MARRY<br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Gamehelpladyschoice&diff=9767
Gamehelpladyschoice
2021-10-18T01:48:54Z
<p>JonChambers: Created page with "To purchase a copy, visit: www.choicegames.com.au Cards Used 70 Gentleman Attributes The Catch The Second Catch 4 Gentleman Player Cards Biological Clock 24 Love 6 True Love..."</p>
<hr />
<div>To purchase a copy, visit: <br />
www.choicegames.com.au<br />
Cards Used<br />
70 Gentleman Attributes<br />
The Catch<br />
The Second Catch<br />
4 Gentleman Player Cards<br />
Biological Clock<br />
24 Love<br />
6 True Love<br />
4 Ranks: Dandiest to Least Dandy<br />
3-6 Player Game<br />
Objective<br />
The player who has received the most Love by the end of the game wins. When you’re The Catch, you receive Love by choosing the Dandiest gentleman you can. When you’re a gentleman, you receive Love by being chosen. Extra Love if you were a terrible choice.<br />
6 Players<br />
If you are not playing with 6 players, you can skip the 6 Players boxes. A 6 player game has a few extra rules.<br />
Setting up the Game<br />
Each player is given Love cards according to the player count:<br />
3 players, each get 2 Love cards<br />
4 players, each get 3 Love cards<br />
5 or 6 players, each get 4 Love cards<br />
These cards represent the Love each player has to give, which should be kept separate from the Love received. The rest of the Love cards are placed back in the box.<br />
Place a supply of True Love cards within reach. <br />
Love to Give cards are stacked “Love to Give” side up, and Love Received cards are splayed so they can be counted at a glance.<br />
<br />
Using Poker Chips<br />
Swapping Love and True Love for red and yellow (golden) poker chips creates a better experience, as your love will have more physical weight, making your victory heavier.<br />
If you do this, your “Love to Give” should be closer to you, while your “Love Received” should be more on display, so your opponents can see how tall the “Love Received” stack is.<br />
Choose someone to be The Catch for the first round, and receive The Catch Player Card. <br />
6 Players<br />
Turn The Catch Player Card over to reveal The First Catch. The player to the right of The First Catch gets The Second Catch Player Card.<br />
All other players play as gentlemen and are each given a Gentleman Player Card. <br />
Round Overview<br />
Everyone gets to be The Catch for one round. Each round is made up of the following phases:<br />
1. Set up Phase: Deal each gentleman 4 Gentleman Attribute cards.<br />
2. Romance Phase: Players take turns in clockwise order, starting with the gentleman to the left of The Catch.<br />
Gentlemen choose to either BOAST or CHANGE on their turn.<br />
The Catch chooses to either COURT or MARRY on her turn.<br />
3. Ranking Phase: When The Catch chooses to MARRY, all gentlemen reveal their hands and total up their Dandy Points. Gentlemen are ranked from Dandiest to Least Dandy.<br />
4. Scoring Phase: Now that they’re married, the husband must return Love to his wife based on his Dandiness ranking.<br />
Pass The Catch card to the left. Start next round.<br />
1. Set up Phase<br />
Shuffle the 70 Gentleman Attribute cards, and deal 4 to each gentleman. The number in the top left corner determines how much more (or less) Dandy the card makes him.<br />
Deal 17 cards face down into a pile and place the “Biological Clock” card on top, then place the remainder of the deck on top of that.<br />
<br />
2. Romance Phase<br />
Play starts with the gentleman to the left of The Catch(es) and proceeds clockwise.<br />
On a Gentleman’s Turn:<br />
Unless the rules state otherwise, a gentleman may not show anyone any of his cards, even if he wants to. (He is, however, free to lie or tell the truth about what cards he has.)<br />
On your turn, select one, and only one, of the following actions:<br />
• BOAST: Choose a card in your hand, show it to everyone, and return it to your hand. Then draw a card.<br />
• CHANGE: Choose a card in your hand, discard it face down, and then draw 2 cards. <br />
You must do whatever is necessary to make sure The Catch(es) don’t know which card was discarded, even if you want them to know. (You are always free to lie or tell the truth about which card was discarded.)<br />
On The Catch’s Turn:<br />
On your turn, choose a gentleman. Then choose one, and only one, of the following actions:<br />
• COURT: The gentleman must shuffle his hand and place his cards face down, such that even he does not know which card is which. Choose and secretly look at a number of cards equal to the number of gentlemen at the table. (If you reveal “Complete Openness” you may see the rest of his hand.) The gentleman is allowed to know which cards you saw.<br />
• MARRY: Give the gentleman all the Love you have to give (i.e. the pile of Love cards you were given at the start of the game. This does not include any Love you received in previous rounds, as this is kept separate.) <br />
6 Players<br />
Once one of the two Catches chooses to MARRY, the remaining Catch must immediately choose a different gentleman to marry. <br />
If the remaining Catch is The Second Catch, she immediately takes an extra COURT action before choosing. <br />
If the remaining Catch is The First Catch, she must choose immediately without the extra action.<br />
Once all Catches have chosen, go straight to ranking phase.<br />
Biological Clock<br />
Once the Biological Clock card is on the top of the deck, place it aside and continue play as normal. (It doesn’t count as a card when drawing.)<br />
Now that it is revealed, The Catch can only choose the Marry action.<br />
6 Players<br />
Revealing the Biological Clock doesn’t affect The First Catch, but The Second Catch can only choose the MARRY action. <br />
3. Ranking Phase<br />
All gentlemen reveal their <br />
cards:<br />
• Cards with “Mysterious” in the title instruct gentlemen to draw more cards after all Catches have married. Do this now. (And so on if another such card is drawn in this way.)<br />
• If a card in your hand “cancels” one or more other cards in your hand, put the cancelled cards aside.<br />
• Cards marked with gold coins are Wealth cards. Find the highest value Wealth card (not yet put aside) and put aside the rest.<br />
Of the cards not put aside, add together the Dandy Points. If any of your cards are worth negative Dandy Points, instead subtract their value from your total.<br />
The gentleman with the most Dandy Points takes the “1st Dandiest” rank card, then the gentleman with the second-most points takes the “2nd Dandiest” rank card, and so on. In the case of a tie, the gentleman with the dandiest Wealth card (not put aside) is dandier. If neither gentleman has a Wealth card remaining, the player with the dandiest card (not put aside) is Dandier. (If somehow this is still a tie, the gentleman earlier in turn order is dandier.)<br />
4. Scoring Phase<br />
The married gentleman (or married gentlemen in a 6 player game) must give 1 Love card that he received from his new wife back to her for each gentleman at the table less Dandy than him. <br />
Example: If The Catch chose the 2nd Dandiest gentleman out of 4, he would give his wife 2 Love back, as 2 gentlemen are less Dandy. If she married the least Dandy gentleman, he would give his wife nothing, as there are no gentlemen less Dandy.<br />
If The Catch married the Dandiest gentleman, he gives her 1 True Love card from the supply in addition to all the other Love cards he gives.<br />
If The Catch married the least Dandy gentleman, he takes 1 True Love card from the supply and keeps it for himself in addition to all the other Love cards he kept for himself.<br />
The Catch(es) now have no more Love to give. (Any Love their husband gave back to them is kept with their Love received.)<br />
Unmarried gentlemen get nothing.<br />
Example<br />
It is a 3 player game. The first gentleman BOASTS “Small Island Country” with a Dandiness of 40, then draws a card. <br />
The second gentleman decides to CHANGE, secretly discarding a card and drawing 2.<br />
Afraid that the first gentleman will discard “Small Island Country” on his next turn, The Catch decides to MARRY him now, giving him all the Love she has to give (2 Love).<br />
All gentlemen reveal.<br />
<br />
The married gentleman (the first gentleman) has “Con Artist” which states that it cancels “Small Island Country”. Of his 3 remaining Wealth cards, “Gold Pocket Watch”, “Silk Top Hat”, and “Automobile”, all cards but the highest card (Automobile) are put aside. The remaining cards total 12 Dandy Points.<br />
The unmarried gentleman (the second gentleman) has “Bankrupt” so he must put aside all his Wealth. The only Wealth card in his hand is “Dirigible”. His remaining cards total 12 Dandy Points.<br />
The gentlemen are tied for Dandiest. The tie is broken by highest Wealth card not put aside. The married (first) gentleman has Automobile, while the unmarried (second) gentleman has no Wealth at all. The married (first) gentleman is therefore the Dandiest.<br />
The married gentleman gives back one Love for each gentleman less Dandy than him, which is 1 Love. As he is the Dandiest, he must also give The Catch a True Love from the supply.<br />
The Catch card moves one player to the left. The new Catch gives the old Catch their Gentleman Player Card.<br />
6 Players<br />
The First Catch and The Second Catch both move two players to the left, swapping for Gentleman Player Cards. A 6 player game is only 3 rounds long, so everyone only gets to be a Catch once.<br />
Set up the next round and keep playing until all players have had a turn to be The Catch. Then, go to Game End.<br />
Game End<br />
The player who has received the most total Love and True Love cards wins.<br />
If two players have the same number of Love cards, the player with the most True Love wins the tie.<br />
6 Players<br />
If playing twice in a row, choose a turn order such that everyone who experienced a turn as “The First Catch” gets to experience a turn as “The Second Catch”<br />
2 Player Game<br />
Objective<br />
Each player plays as a lady and 4 piles of cards represent gentlemen. Ladies take turns courting the gentlemen until one lady is ready to marry. If she marries the dandiest gentleman, she wins. If she marries any other gentleman, the other lady wins.<br />
Setup<br />
Deal 4 piles of 6 cards between both players making sure there is room for more cards on each side as pictured:<br />
Gameplay<br />
On the starting player’s first turn, she must take 1 COURT action. On all subsequent turns, the active player may choose to take 2 COURT actions or MARRY.<br />
COURT Action<br />
Choose a gentleman (pile of cards), draw the top card, look at it, and then place it on your side face down, on top of the space nearest the deck it came from. (This forms the deck of things you know about this gentleman.)<br />
If the pile begins this action empty or face up, instead take the top card from the nearest space on your opponent’s side, flip it face up, and place it where the original pile was. (This represents what you both know about the gentleman.)<br />
(You may look at the cards on your side, or piles of face up cards, at any time, but you must put them back in the same order.)<br />
MARRY<br />
Choose a gentleman to MARRY.<br />
Following the rules for “Ranking Phase” in the 3-6 player version: If any gentleman is ranked higher than the one you chose, the other lady wins. If not, you win.<br />
(Turn order ties break in favour of the married gentleman)</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Troubleshooting&diff=9487
Troubleshooting
2021-09-28T02:09:57Z
<p>JonChambers: /* Unexpected error: Can't manage zombie player in this game state */</p>
<hr />
<div><br />
Describing common errors which is hard to understand and debug <br />
<br />
__TOC__<br />
<br />
<br />
== Game does not start at all ==<br />
<br />
=== Undefined offset: 0 in table/table.game.php on line 830 ===<br />
<br />
Check if you're calling self::getActivePlayerName () during setupNewGame()<br />
<br />
Check if you're NOT calling self::activeNextPlayer() at the end of your game setup. You must always have at least one active player.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: createGame): ... ===<br />
<br />
This is generic message usually followed by exact position in your source code, and usually its syntax error in one of yours php script<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Not logged ===<br />
<br />
Calling self::getCurrentPlayerId () or using $g_user from 'args' state function, see also below<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Unknow player statistic: ===<br />
<br />
Calling self::incStat() with second parameter which is an empty string<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats ... ===<br />
<br />
Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats (stats_type, stats_player_id, stats_value) VALUES ('10','2300663','0'),('10','2300662','0')<br />
Duplicate entry '10-2300663' for key 'stats_table_id'<br />
<br />
Why? In the stats.inc.php you declared two keys with the same integer "id"<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: BGA main website do not respond ===<br />
<br />
Check if other games work; if not, it's a problem with BGA Studio; if so, your game likely reaches an end state immediately. Check your states.inc.php and your transitions.<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): BGA service error ===<br />
<br />
You may have a syntax error in your dbmodel.sql.<br />
<br />
Or, you may be calling self::initStat() for a statistic that does not exist. Check the statistic name, and make sure you clicked "Reload statistics configuration" button in the admin console after making changes to stats.inc.php<br />
<br />
Or you may not have your states.inc.php set up correctly.<br />
<br />
You may have an error in your table.game.php file (such as <code>$result['x'] = y;</code> when $result is not an array).<br />
<br />
=== Fatal error during creation of database ebd_quoridor_389 Not logged ===<br />
<br />
Check that you didn't use $g_user or getCurrentPlayerId() in setupNewGame() function or in an 'args' function of your state.<br />
<br />
As these functions are not consequences of a user action, there is no current player defined.<br />
<br />
As a general rule, you should use getActivePlayerId() and not getCurrentPlayerId(). See the [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine presentation on the game state machine] for more information.<br />
<br />
=== Warning: Invalid argument supplied for foreach() in table.game.php ===<br />
<br />
Warning: Invalid argument supplied for foreach() in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 129 <br />
Fatal error: Cannot unset string offsets in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 143<br />
<br />
That appears when your arg* function that suppose to return array of state arguments returns a scalar (a non-array) value<br />
<br />
=== The server reported an error ===<br />
<br />
During table creation: "The server reported an error" error shown and nothing else.<br />
<br />
If you cannot even create a table - there is syntax error in gameinfos.php, check it, reload it from management panel.<br />
If still no luck copy clean version from template https://github.com/elaskavaia/bga-sharedcode/blob/master/gameinfos.inc.php<br />
<br />
== Game starts but I can't make a move ==<br />
<br />
=== When I do a move, I got "Move recorded, waiting for update ..." forever ===<br />
<br />
"Move recorded" means that your ajaxcall request has been sent to the server and returned normally.<br />
<br />
"Waiting for update" means that your client interface is waiting for some notifications from the server that correspond to the move we just did.<br />
<br />
If this message stays forever, it is probably that your PHP code does not send any notification when the move happens, which is abnormal. To fix this: add a notifyAllPlayers or a notifyPlayer call in your PHP code.<br />
<br />
=== When I do a move, I get "Sending move to server..." then nothing and game resets to state before the move ===<br />
<br />
Its possible that server code get into infinite loops or thinks too much, in which case it will timeout and will be aborted without any extra logs (and db transaction you saw in the log won't be committed). You will usually see "Unable to connect to server" message on console in this case. You have to put more logging into server<br />
to trace where it hangs.<br />
<br />
=== When I do a move, I get "Ajaxcall error: empty answer" ===<br />
<br />
You either have missing self::ajaxResponse(); in your game.action.php method, or there is some problem with getting arguments (check that you are using btoa() on client when using AT_base64 arg type in action.php)<br />
<br />
=== Some player action is triggered randomly when I click somewhere on the game area ===<br />
<br />
You probably used "dojo.connect" on a null object. In this case, dojo.connect associate the event (ex: "onclick") to the whole game area.<br />
<br />
Most of the time it happens in this situation, when my_object element does not exists:<br />
<pre><br />
dojo.connect( $("my_object"), "onclick", this, (event)=>{})<br />
</pre><br />
<br />
To determine if this is the case, place "alert( $("my_object") )" before the dojo.connect to check if the object exists or not.<br />
<br />
=== Error: This is not your turn ===<br />
<br />
This happens when checkAction() is called and current player is NOT the active player.<br />
<br />
You can change the active player in studio by clicking the red arrow next to their name in the player panel (right side of the page).<br />
<br />
[[File:Change_active_player.jpg]]<br />
<br />
=== Error: Please wait, an action is already in progress ===<br />
Its followed by error "(Generated by checkAction / XXX)" on studio.<br />
<br />
This error is triggered in the JS by the checkAction function for XXX action.<br />
<br />
This can happen when the player sent an Ajax request action earlier, and:<br />
* action is not complete yet - i.e. takes some time, and user clicked something again <br />
* if there is bug in interface where MULTIPLE handlers are fired on same click. To debug this add some console.trace() in place this specific checkAction is called.<br />
* did not get any notification from the PHP side. To avoid this, put an empty notifyPlayer in the PHP side of the Ajax handler to ensure the client receives a response. This line of code should do it:<br />
<pre><br />
$this->notifyPlayer($this->getCurrentPlayerId(), 'message', '', []); // sent to current player, who originated the action<br />
</pre><br />
<br />
=== Error: This move is not authorized now ===<br />
This is generic error message generated by this.showMoveUnauthorized() function. If you did not call this function yourself, check the console log, it may have additional information.<br />
If you see "Move not authorized now : XXX" in the log, where XXX is your action name - this means that action XXX is not defined in list of possible actions for your current state. This error generated by checkAction() function.<br />
<br />
== Predefined server errors ==<br />
<br />
=== Unexpected error: Unexpected final game state (XX) ===<br />
<br />
The action function does not transition to any state, i.e.<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
}<br />
Here if $field is 0 there is no transition<br />
<br />
=== This game action is impossible right now ===<br />
<br />
Check the game log. Usually your state does not define the action you trying to perform in 'possibleactions' array.<br />
<br />
Also check your onActionName javascript function. Make sure you are calling the right "actionname.html".<br />
<br />
=== Unexpected error: This transition (playerTurn) is impossible at this state (42) ===<br />
<br />
This is pretty self explanatory. Function nextState() takes transition name not a state name, so you probably did not<br />
define this transition that the given state<br />
<br />
== Game interface hangs during reload or on start ==<br />
<br />
Showing "Application Loading..."<br />
<br />
=== Javascript error: During pageload undefined no_stack_avail Script: ===<br />
<br />
This error usually has no useful data, but it means you called somes API that require a callback and did not define callback function, i.e<br />
in dojo.connect, this.connectClass, dojo.subscribe, etc<br />
<br />
this.connectClass('field', 'onclick', 'onField'); // <-- onField is not defined<br />
<br />
=== Other errors with "Application loading..." ===<br />
<br />
You probably have a syntax error in your Javascript code, and the interface refuses to load.<br />
<br />
To find this error, check if there is an error message in the Javascript console (F12).<br />
<br />
If there is really nothing on the log, it's probably that the system was unable to load your Javascript because of an syntax error that affect the structure of the Javascript file, typically a missing "}" or a missing "," after a method definition.<br />
<br />
If you have "Uncaught ReferenceError: bgagame is not defined" you have major syntax error in your js file, you should see some other clues in the log where the errors is.<br />
<br />
If you have "dojo.publish is not defined" or something similar, this could be caused by another error earlier. For example, a syntax error caused by a malformed generated file (such as using newlines in tiebreaker description). Also you might have left some images in the /img directory with special characters in the name which must be removed. If you edited images for the game box and website, ensure that you have not left any unnecessary files there.<br />
<br />
=== Unexpected Syntax Error: ===<br />
<br />
No further details in the log. When log is filling with some social connect errors.<br />
<br />
Possible Reason: Syntax error in of the php script which is loaded before the start, such as gameoptions.inc.php, gameinfos.inc.php and such.<br />
<br />
=== Game interface spins in a loop throwing error ===<br />
<br />
Errors is something like "Cannot read property 'is_ai' of undefined". Cannot restart the game because cannot access UI to stop.<br />
Likely you get in actplayer state with player id == 0. The only way to fix it is to edit database, globals index == 2 set player id to one of your test dudes<br />
(can copy from row 5 for example).<br />
<br />
=== Unable to find table database (1): ebd_yourgame_112222 ===<br />
<br />
Its either temporarily server error especially on studio it timeouts sometimes, just try reloading again.<br />
Or check if you have syntax errors in your php game file or sql.<br />
<br />
== Type conversion / juggling errors ==<br />
<br />
=== On php side I get a number instead of string I expect ===<br />
<br />
$num = 3;<br />
$meeple = "meeple_" + $num; // <-- suppose to be "meeple_3"!<br />
<br />
When you switch between JS and PHP it easy to type this and not notice the +. Plus sign (+) in php does not mean string concatenation (in javascript does!),<br />
in php + means integer arithmetic. So change + to . (dot)<br />
<br />
=== On php side my string comparison does not work ===<br />
<br />
if ($color == '4baae2' || $color == '000000') { <br />
}<br />
<br />
Apparently you should not be using '==' in php to compare strings! You should use '==='. The (==) operator will typecast the strings <br />
to numbers then do comparison!<br />
Its not very apparent because usually you can get away with it, but not when strings resemble numbers like hex 'colors'.<br />
<br />
=== Integer columns in the database are returned as strings ===<br />
<br />
This is normal PHP behavior. All fields are returned as string type, regardless of their actual type in the database. This also applies to global variables accessed via getGameStateValue(), since they are stored in a database table.<br />
<br />
You can use type-casting to convert them to the correct type after they come out of the database.<br />
<br />
$myValueInt = (int)self::getGameStateValue(GLOBAL_ROUND_NUMBER);<br />
<br />
or<br />
<br />
$myValueInt = self::getGameStateValue(GLOBAL_ROUND_NUMBER) + 0; // adding 0 casts to int<br />
<br />
<br />
same with direct qdb ueries<br />
$sql = "SELECT my_int_column FROM my_table WHERE my_condition";<br />
$myResult = self::getUniqueValueFromDB($sql); <-- this is a string<br />
$myResultAsInt = (int)$myResult;<br />
<br />
https://stackoverflow.com/questions/5323146/mysql-integer-field-is-returned-as-string-in-php<br />
<br />
== Zombie mode ==<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: zombie): Not logged ===<br />
<br />
You are probably calling getCurrentPlayerId() or getCurrentPlayerName() in your zombieTurn method or any of the methods it uses. Instead, use the $active_player_id provided as parameter to zombieTurn().<br />
<br />
If you do not see these commands within "function zombieTurn( $state, $active_player ){}", check all the functions called from zombieTurn, also "function getGameProgression(){}" and all the functions THAT calls.<br />
<br />
=== Unexpected error: Can't manage zombie player in this game state ===<br />
<br />
The state the game was in at the time the error was generated didn't have "transitions" => array( "zombiePass" => 27 )" in states.inc.php (27 was an arbitrary example, but everything else must be spelled exactly that way.)<br />
<br />
To correct this error, either add "zombiePass" => ## to the transitions array or work out why the game is in a state it should not be in.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: zombie): ===<br />
<br />
This is almost certainly an undefined value in PHP code. Look for a warning or error message in the game replay log on the right side of the table UI.<br />
<br />
=== Unexpected error: BGA gameserver 1 do not respond (method: zombie) (timeout: cluster) ===<br />
<br />
You are trying to end the game from a zombie method. This is not allowed. The zombie logic must continue the game as best it can. See [[Main_game_logic:_yourgamename.game.php#Zombie_mode|Zombie mode]] for more info.<br />
<br />
=== Game unexpectedly ends when one player becomes zombie ===<br />
<br />
There is an error in handling zombie mode, check error log. Example: if the zombie turn is called again, framework detects a infinite loop and cancels the game. On the studio there is a notification called ZombieTurnFailed<br />
<br />
== Other errors ==<br />
<br />
=== Javascript does not know how to sum two numbers ===<br />
<br />
Be careful when you manipulate integers returned by notifications: most of the time, Javascript considers they are Strings and not Integers.<br />
<br />
As a result:<br />
<pre><br />
var i=1;<br />
i += notif.args.increment; // With notif.args.increment='1'<br />
alert( i ); // i=11 instead of 2 !! Javascript concatenate 2 strings !<br />
</pre><br />
<br />
To solve this, you should use the "toint" function:<br />
<pre><br />
var i=1;<br />
i += toint( notif.args.increment ); // With notif.args.increment='1'<br />
alert( i ); // i=2 :)<br />
</pre><br />
<br />
=== Javascript: do not use substr with negative numbers ===<br />
<br />
To get the last characters of a string, use "slice" instead of "substr" which has a bug on IE:<br />
<pre><br />
var three_last_characters = string.substr( -3 ); // Wrong<br />
var three_last_characters = string.slice( -3 ); // Correct<br />
</pre><br />
<br />
=== Game "spontaneously" transition to a new state without user input ===<br />
<br />
Make sure on php side you have no code after $this->gamestate->nextState(...) code.<br />
Because if you do accidentally have code that goes to another state it will cause another state transition without user interaction.<br />
<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
$this->gamestate->nextState ( 'last' ); // <-- here is missing else, so it will cause double state transition<br />
}</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Troubleshooting&diff=9486
Troubleshooting
2021-09-28T02:09:02Z
<p>JonChambers: /* Unexpected error: Propagating error from GS 1 (method: zombie): Not logged */</p>
<hr />
<div><br />
Describing common errors which is hard to understand and debug <br />
<br />
__TOC__<br />
<br />
<br />
== Game does not start at all ==<br />
<br />
=== Undefined offset: 0 in table/table.game.php on line 830 ===<br />
<br />
Check if you're calling self::getActivePlayerName () during setupNewGame()<br />
<br />
Check if you're NOT calling self::activeNextPlayer() at the end of your game setup. You must always have at least one active player.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: createGame): ... ===<br />
<br />
This is generic message usually followed by exact position in your source code, and usually its syntax error in one of yours php script<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Not logged ===<br />
<br />
Calling self::getCurrentPlayerId () or using $g_user from 'args' state function, see also below<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Unknow player statistic: ===<br />
<br />
Calling self::incStat() with second parameter which is an empty string<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats ... ===<br />
<br />
Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats (stats_type, stats_player_id, stats_value) VALUES ('10','2300663','0'),('10','2300662','0')<br />
Duplicate entry '10-2300663' for key 'stats_table_id'<br />
<br />
Why? In the stats.inc.php you declared two keys with the same integer "id"<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: BGA main website do not respond ===<br />
<br />
Check if other games work; if not, it's a problem with BGA Studio; if so, your game likely reaches an end state immediately. Check your states.inc.php and your transitions.<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): BGA service error ===<br />
<br />
You may have a syntax error in your dbmodel.sql.<br />
<br />
Or, you may be calling self::initStat() for a statistic that does not exist. Check the statistic name, and make sure you clicked "Reload statistics configuration" button in the admin console after making changes to stats.inc.php<br />
<br />
Or you may not have your states.inc.php set up correctly.<br />
<br />
You may have an error in your table.game.php file (such as <code>$result['x'] = y;</code> when $result is not an array).<br />
<br />
=== Fatal error during creation of database ebd_quoridor_389 Not logged ===<br />
<br />
Check that you didn't use $g_user or getCurrentPlayerId() in setupNewGame() function or in an 'args' function of your state.<br />
<br />
As these functions are not consequences of a user action, there is no current player defined.<br />
<br />
As a general rule, you should use getActivePlayerId() and not getCurrentPlayerId(). See the [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine presentation on the game state machine] for more information.<br />
<br />
=== Warning: Invalid argument supplied for foreach() in table.game.php ===<br />
<br />
Warning: Invalid argument supplied for foreach() in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 129 <br />
Fatal error: Cannot unset string offsets in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 143<br />
<br />
That appears when your arg* function that suppose to return array of state arguments returns a scalar (a non-array) value<br />
<br />
=== The server reported an error ===<br />
<br />
During table creation: "The server reported an error" error shown and nothing else.<br />
<br />
If you cannot even create a table - there is syntax error in gameinfos.php, check it, reload it from management panel.<br />
If still no luck copy clean version from template https://github.com/elaskavaia/bga-sharedcode/blob/master/gameinfos.inc.php<br />
<br />
== Game starts but I can't make a move ==<br />
<br />
=== When I do a move, I got "Move recorded, waiting for update ..." forever ===<br />
<br />
"Move recorded" means that your ajaxcall request has been sent to the server and returned normally.<br />
<br />
"Waiting for update" means that your client interface is waiting for some notifications from the server that correspond to the move we just did.<br />
<br />
If this message stays forever, it is probably that your PHP code does not send any notification when the move happens, which is abnormal. To fix this: add a notifyAllPlayers or a notifyPlayer call in your PHP code.<br />
<br />
=== When I do a move, I get "Sending move to server..." then nothing and game resets to state before the move ===<br />
<br />
Its possible that server code get into infinite loops or thinks too much, in which case it will timeout and will be aborted without any extra logs (and db transaction you saw in the log won't be committed). You will usually see "Unable to connect to server" message on console in this case. You have to put more logging into server<br />
to trace where it hangs.<br />
<br />
=== When I do a move, I get "Ajaxcall error: empty answer" ===<br />
<br />
You either have missing self::ajaxResponse(); in your game.action.php method, or there is some problem with getting arguments (check that you are using btoa() on client when using AT_base64 arg type in action.php)<br />
<br />
=== Some player action is triggered randomly when I click somewhere on the game area ===<br />
<br />
You probably used "dojo.connect" on a null object. In this case, dojo.connect associate the event (ex: "onclick") to the whole game area.<br />
<br />
Most of the time it happens in this situation, when my_object element does not exists:<br />
<pre><br />
dojo.connect( $("my_object"), "onclick", this, (event)=>{})<br />
</pre><br />
<br />
To determine if this is the case, place "alert( $("my_object") )" before the dojo.connect to check if the object exists or not.<br />
<br />
=== Error: This is not your turn ===<br />
<br />
This happens when checkAction() is called and current player is NOT the active player.<br />
<br />
You can change the active player in studio by clicking the red arrow next to their name in the player panel (right side of the page).<br />
<br />
[[File:Change_active_player.jpg]]<br />
<br />
=== Error: Please wait, an action is already in progress ===<br />
Its followed by error "(Generated by checkAction / XXX)" on studio.<br />
<br />
This error is triggered in the JS by the checkAction function for XXX action.<br />
<br />
This can happen when the player sent an Ajax request action earlier, and:<br />
* action is not complete yet - i.e. takes some time, and user clicked something again <br />
* if there is bug in interface where MULTIPLE handlers are fired on same click. To debug this add some console.trace() in place this specific checkAction is called.<br />
* did not get any notification from the PHP side. To avoid this, put an empty notifyPlayer in the PHP side of the Ajax handler to ensure the client receives a response. This line of code should do it:<br />
<pre><br />
$this->notifyPlayer($this->getCurrentPlayerId(), 'message', '', []); // sent to current player, who originated the action<br />
</pre><br />
<br />
=== Error: This move is not authorized now ===<br />
This is generic error message generated by this.showMoveUnauthorized() function. If you did not call this function yourself, check the console log, it may have additional information.<br />
If you see "Move not authorized now : XXX" in the log, where XXX is your action name - this means that action XXX is not defined in list of possible actions for your current state. This error generated by checkAction() function.<br />
<br />
== Predefined server errors ==<br />
<br />
=== Unexpected error: Unexpected final game state (XX) ===<br />
<br />
The action function does not transition to any state, i.e.<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
}<br />
Here if $field is 0 there is no transition<br />
<br />
=== This game action is impossible right now ===<br />
<br />
Check the game log. Usually your state does not define the action you trying to perform in 'possibleactions' array.<br />
<br />
Also check your onActionName javascript function. Make sure you are calling the right "actionname.html".<br />
<br />
=== Unexpected error: This transition (playerTurn) is impossible at this state (42) ===<br />
<br />
This is pretty self explanatory. Function nextState() takes transition name not a state name, so you probably did not<br />
define this transition that the given state<br />
<br />
== Game interface hangs during reload or on start ==<br />
<br />
Showing "Application Loading..."<br />
<br />
=== Javascript error: During pageload undefined no_stack_avail Script: ===<br />
<br />
This error usually has no useful data, but it means you called somes API that require a callback and did not define callback function, i.e<br />
in dojo.connect, this.connectClass, dojo.subscribe, etc<br />
<br />
this.connectClass('field', 'onclick', 'onField'); // <-- onField is not defined<br />
<br />
=== Other errors with "Application loading..." ===<br />
<br />
You probably have a syntax error in your Javascript code, and the interface refuses to load.<br />
<br />
To find this error, check if there is an error message in the Javascript console (F12).<br />
<br />
If there is really nothing on the log, it's probably that the system was unable to load your Javascript because of an syntax error that affect the structure of the Javascript file, typically a missing "}" or a missing "," after a method definition.<br />
<br />
If you have "Uncaught ReferenceError: bgagame is not defined" you have major syntax error in your js file, you should see some other clues in the log where the errors is.<br />
<br />
If you have "dojo.publish is not defined" or something similar, this could be caused by another error earlier. For example, a syntax error caused by a malformed generated file (such as using newlines in tiebreaker description). Also you might have left some images in the /img directory with special characters in the name which must be removed. If you edited images for the game box and website, ensure that you have not left any unnecessary files there.<br />
<br />
=== Unexpected Syntax Error: ===<br />
<br />
No further details in the log. When log is filling with some social connect errors.<br />
<br />
Possible Reason: Syntax error in of the php script which is loaded before the start, such as gameoptions.inc.php, gameinfos.inc.php and such.<br />
<br />
=== Game interface spins in a loop throwing error ===<br />
<br />
Errors is something like "Cannot read property 'is_ai' of undefined". Cannot restart the game because cannot access UI to stop.<br />
Likely you get in actplayer state with player id == 0. The only way to fix it is to edit database, globals index == 2 set player id to one of your test dudes<br />
(can copy from row 5 for example).<br />
<br />
=== Unable to find table database (1): ebd_yourgame_112222 ===<br />
<br />
Its either temporarily server error especially on studio it timeouts sometimes, just try reloading again.<br />
Or check if you have syntax errors in your php game file or sql.<br />
<br />
== Type conversion / juggling errors ==<br />
<br />
=== On php side I get a number instead of string I expect ===<br />
<br />
$num = 3;<br />
$meeple = "meeple_" + $num; // <-- suppose to be "meeple_3"!<br />
<br />
When you switch between JS and PHP it easy to type this and not notice the +. Plus sign (+) in php does not mean string concatenation (in javascript does!),<br />
in php + means integer arithmetic. So change + to . (dot)<br />
<br />
=== On php side my string comparison does not work ===<br />
<br />
if ($color == '4baae2' || $color == '000000') { <br />
}<br />
<br />
Apparently you should not be using '==' in php to compare strings! You should use '==='. The (==) operator will typecast the strings <br />
to numbers then do comparison!<br />
Its not very apparent because usually you can get away with it, but not when strings resemble numbers like hex 'colors'.<br />
<br />
=== Integer columns in the database are returned as strings ===<br />
<br />
This is normal PHP behavior. All fields are returned as string type, regardless of their actual type in the database. This also applies to global variables accessed via getGameStateValue(), since they are stored in a database table.<br />
<br />
You can use type-casting to convert them to the correct type after they come out of the database.<br />
<br />
$myValueInt = (int)self::getGameStateValue(GLOBAL_ROUND_NUMBER);<br />
<br />
or<br />
<br />
$myValueInt = self::getGameStateValue(GLOBAL_ROUND_NUMBER) + 0; // adding 0 casts to int<br />
<br />
<br />
same with direct qdb ueries<br />
$sql = "SELECT my_int_column FROM my_table WHERE my_condition";<br />
$myResult = self::getUniqueValueFromDB($sql); <-- this is a string<br />
$myResultAsInt = (int)$myResult;<br />
<br />
https://stackoverflow.com/questions/5323146/mysql-integer-field-is-returned-as-string-in-php<br />
<br />
== Zombie mode ==<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: zombie): Not logged ===<br />
<br />
You are probably calling getCurrentPlayerId() or getCurrentPlayerName() in your zombieTurn method or any of the methods it uses. Instead, use the $active_player_id provided as parameter to zombieTurn().<br />
<br />
If you do not see these commands within "function zombieTurn( $state, $active_player ){}", check all the functions called from zombieTurn, also "function getGameProgression(){}" and all the functions THAT calls.<br />
<br />
=== Unexpected error: Can't manage zombie player in this game state ===<br />
<br />
The state the game was in at the time the error was generated didn't have "transitions" => array( "zombiePass" => 27 )" (27 was an arbitrary example, but everything else must be spelled exactly that way.)<br />
<br />
To correct this error, either add "zombiePass" => ## to the transitions array or work out why the game is in a state it should not be in.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: zombie): ===<br />
<br />
This is almost certainly an undefined value in PHP code. Look for a warning or error message in the game replay log on the right side of the table UI.<br />
<br />
=== Unexpected error: BGA gameserver 1 do not respond (method: zombie) (timeout: cluster) ===<br />
<br />
You are trying to end the game from a zombie method. This is not allowed. The zombie logic must continue the game as best it can. See [[Main_game_logic:_yourgamename.game.php#Zombie_mode|Zombie mode]] for more info.<br />
<br />
=== Game unexpectedly ends when one player becomes zombie ===<br />
<br />
There is an error in handling zombie mode, check error log. Example: if the zombie turn is called again, framework detects a infinite loop and cancels the game. On the studio there is a notification called ZombieTurnFailed<br />
<br />
== Other errors ==<br />
<br />
=== Javascript does not know how to sum two numbers ===<br />
<br />
Be careful when you manipulate integers returned by notifications: most of the time, Javascript considers they are Strings and not Integers.<br />
<br />
As a result:<br />
<pre><br />
var i=1;<br />
i += notif.args.increment; // With notif.args.increment='1'<br />
alert( i ); // i=11 instead of 2 !! Javascript concatenate 2 strings !<br />
</pre><br />
<br />
To solve this, you should use the "toint" function:<br />
<pre><br />
var i=1;<br />
i += toint( notif.args.increment ); // With notif.args.increment='1'<br />
alert( i ); // i=2 :)<br />
</pre><br />
<br />
=== Javascript: do not use substr with negative numbers ===<br />
<br />
To get the last characters of a string, use "slice" instead of "substr" which has a bug on IE:<br />
<pre><br />
var three_last_characters = string.substr( -3 ); // Wrong<br />
var three_last_characters = string.slice( -3 ); // Correct<br />
</pre><br />
<br />
=== Game "spontaneously" transition to a new state without user input ===<br />
<br />
Make sure on php side you have no code after $this->gamestate->nextState(...) code.<br />
Because if you do accidentally have code that goes to another state it will cause another state transition without user interaction.<br />
<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
$this->gamestate->nextState ( 'last' ); // <-- here is missing else, so it will cause double state transition<br />
}</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Troubleshooting&diff=9485
Troubleshooting
2021-09-28T02:05:34Z
<p>JonChambers: The text previously here was not only false, but the exact opposite of the truth. Reading it, it was not only unhelpful but actually put me on the wrong path to solving it.</p>
<hr />
<div><br />
Describing common errors which is hard to understand and debug <br />
<br />
__TOC__<br />
<br />
<br />
== Game does not start at all ==<br />
<br />
=== Undefined offset: 0 in table/table.game.php on line 830 ===<br />
<br />
Check if you're calling self::getActivePlayerName () during setupNewGame()<br />
<br />
Check if you're NOT calling self::activeNextPlayer() at the end of your game setup. You must always have at least one active player.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: createGame): ... ===<br />
<br />
This is generic message usually followed by exact position in your source code, and usually its syntax error in one of yours php script<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Not logged ===<br />
<br />
Calling self::getCurrentPlayerId () or using $g_user from 'args' state function, see also below<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Unknow player statistic: ===<br />
<br />
Calling self::incStat() with second parameter which is an empty string<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats ... ===<br />
<br />
Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats (stats_type, stats_player_id, stats_value) VALUES ('10','2300663','0'),('10','2300662','0')<br />
Duplicate entry '10-2300663' for key 'stats_table_id'<br />
<br />
Why? In the stats.inc.php you declared two keys with the same integer "id"<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: BGA main website do not respond ===<br />
<br />
Check if other games work; if not, it's a problem with BGA Studio; if so, your game likely reaches an end state immediately. Check your states.inc.php and your transitions.<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): BGA service error ===<br />
<br />
You may have a syntax error in your dbmodel.sql.<br />
<br />
Or, you may be calling self::initStat() for a statistic that does not exist. Check the statistic name, and make sure you clicked "Reload statistics configuration" button in the admin console after making changes to stats.inc.php<br />
<br />
Or you may not have your states.inc.php set up correctly.<br />
<br />
You may have an error in your table.game.php file (such as <code>$result['x'] = y;</code> when $result is not an array).<br />
<br />
=== Fatal error during creation of database ebd_quoridor_389 Not logged ===<br />
<br />
Check that you didn't use $g_user or getCurrentPlayerId() in setupNewGame() function or in an 'args' function of your state.<br />
<br />
As these functions are not consequences of a user action, there is no current player defined.<br />
<br />
As a general rule, you should use getActivePlayerId() and not getCurrentPlayerId(). See the [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine presentation on the game state machine] for more information.<br />
<br />
=== Warning: Invalid argument supplied for foreach() in table.game.php ===<br />
<br />
Warning: Invalid argument supplied for foreach() in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 129 <br />
Fatal error: Cannot unset string offsets in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 143<br />
<br />
That appears when your arg* function that suppose to return array of state arguments returns a scalar (a non-array) value<br />
<br />
=== The server reported an error ===<br />
<br />
During table creation: "The server reported an error" error shown and nothing else.<br />
<br />
If you cannot even create a table - there is syntax error in gameinfos.php, check it, reload it from management panel.<br />
If still no luck copy clean version from template https://github.com/elaskavaia/bga-sharedcode/blob/master/gameinfos.inc.php<br />
<br />
== Game starts but I can't make a move ==<br />
<br />
=== When I do a move, I got "Move recorded, waiting for update ..." forever ===<br />
<br />
"Move recorded" means that your ajaxcall request has been sent to the server and returned normally.<br />
<br />
"Waiting for update" means that your client interface is waiting for some notifications from the server that correspond to the move we just did.<br />
<br />
If this message stays forever, it is probably that your PHP code does not send any notification when the move happens, which is abnormal. To fix this: add a notifyAllPlayers or a notifyPlayer call in your PHP code.<br />
<br />
=== When I do a move, I get "Sending move to server..." then nothing and game resets to state before the move ===<br />
<br />
Its possible that server code get into infinite loops or thinks too much, in which case it will timeout and will be aborted without any extra logs (and db transaction you saw in the log won't be committed). You will usually see "Unable to connect to server" message on console in this case. You have to put more logging into server<br />
to trace where it hangs.<br />
<br />
=== When I do a move, I get "Ajaxcall error: empty answer" ===<br />
<br />
You either have missing self::ajaxResponse(); in your game.action.php method, or there is some problem with getting arguments (check that you are using btoa() on client when using AT_base64 arg type in action.php)<br />
<br />
=== Some player action is triggered randomly when I click somewhere on the game area ===<br />
<br />
You probably used "dojo.connect" on a null object. In this case, dojo.connect associate the event (ex: "onclick") to the whole game area.<br />
<br />
Most of the time it happens in this situation, when my_object element does not exists:<br />
<pre><br />
dojo.connect( $("my_object"), "onclick", this, (event)=>{})<br />
</pre><br />
<br />
To determine if this is the case, place "alert( $("my_object") )" before the dojo.connect to check if the object exists or not.<br />
<br />
=== Error: This is not your turn ===<br />
<br />
This happens when checkAction() is called and current player is NOT the active player.<br />
<br />
You can change the active player in studio by clicking the red arrow next to their name in the player panel (right side of the page).<br />
<br />
[[File:Change_active_player.jpg]]<br />
<br />
=== Error: Please wait, an action is already in progress ===<br />
Its followed by error "(Generated by checkAction / XXX)" on studio.<br />
<br />
This error is triggered in the JS by the checkAction function for XXX action.<br />
<br />
This can happen when the player sent an Ajax request action earlier, and:<br />
* action is not complete yet - i.e. takes some time, and user clicked something again <br />
* if there is bug in interface where MULTIPLE handlers are fired on same click. To debug this add some console.trace() in place this specific checkAction is called.<br />
* did not get any notification from the PHP side. To avoid this, put an empty notifyPlayer in the PHP side of the Ajax handler to ensure the client receives a response. This line of code should do it:<br />
<pre><br />
$this->notifyPlayer($this->getCurrentPlayerId(), 'message', '', []); // sent to current player, who originated the action<br />
</pre><br />
<br />
=== Error: This move is not authorized now ===<br />
This is generic error message generated by this.showMoveUnauthorized() function. If you did not call this function yourself, check the console log, it may have additional information.<br />
If you see "Move not authorized now : XXX" in the log, where XXX is your action name - this means that action XXX is not defined in list of possible actions for your current state. This error generated by checkAction() function.<br />
<br />
== Predefined server errors ==<br />
<br />
=== Unexpected error: Unexpected final game state (XX) ===<br />
<br />
The action function does not transition to any state, i.e.<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
}<br />
Here if $field is 0 there is no transition<br />
<br />
=== This game action is impossible right now ===<br />
<br />
Check the game log. Usually your state does not define the action you trying to perform in 'possibleactions' array.<br />
<br />
Also check your onActionName javascript function. Make sure you are calling the right "actionname.html".<br />
<br />
=== Unexpected error: This transition (playerTurn) is impossible at this state (42) ===<br />
<br />
This is pretty self explanatory. Function nextState() takes transition name not a state name, so you probably did not<br />
define this transition that the given state<br />
<br />
== Game interface hangs during reload or on start ==<br />
<br />
Showing "Application Loading..."<br />
<br />
=== Javascript error: During pageload undefined no_stack_avail Script: ===<br />
<br />
This error usually has no useful data, but it means you called somes API that require a callback and did not define callback function, i.e<br />
in dojo.connect, this.connectClass, dojo.subscribe, etc<br />
<br />
this.connectClass('field', 'onclick', 'onField'); // <-- onField is not defined<br />
<br />
=== Other errors with "Application loading..." ===<br />
<br />
You probably have a syntax error in your Javascript code, and the interface refuses to load.<br />
<br />
To find this error, check if there is an error message in the Javascript console (F12).<br />
<br />
If there is really nothing on the log, it's probably that the system was unable to load your Javascript because of an syntax error that affect the structure of the Javascript file, typically a missing "}" or a missing "," after a method definition.<br />
<br />
If you have "Uncaught ReferenceError: bgagame is not defined" you have major syntax error in your js file, you should see some other clues in the log where the errors is.<br />
<br />
If you have "dojo.publish is not defined" or something similar, this could be caused by another error earlier. For example, a syntax error caused by a malformed generated file (such as using newlines in tiebreaker description). Also you might have left some images in the /img directory with special characters in the name which must be removed. If you edited images for the game box and website, ensure that you have not left any unnecessary files there.<br />
<br />
=== Unexpected Syntax Error: ===<br />
<br />
No further details in the log. When log is filling with some social connect errors.<br />
<br />
Possible Reason: Syntax error in of the php script which is loaded before the start, such as gameoptions.inc.php, gameinfos.inc.php and such.<br />
<br />
=== Game interface spins in a loop throwing error ===<br />
<br />
Errors is something like "Cannot read property 'is_ai' of undefined". Cannot restart the game because cannot access UI to stop.<br />
Likely you get in actplayer state with player id == 0. The only way to fix it is to edit database, globals index == 2 set player id to one of your test dudes<br />
(can copy from row 5 for example).<br />
<br />
=== Unable to find table database (1): ebd_yourgame_112222 ===<br />
<br />
Its either temporarily server error especially on studio it timeouts sometimes, just try reloading again.<br />
Or check if you have syntax errors in your php game file or sql.<br />
<br />
== Type conversion / juggling errors ==<br />
<br />
=== On php side I get a number instead of string I expect ===<br />
<br />
$num = 3;<br />
$meeple = "meeple_" + $num; // <-- suppose to be "meeple_3"!<br />
<br />
When you switch between JS and PHP it easy to type this and not notice the +. Plus sign (+) in php does not mean string concatenation (in javascript does!),<br />
in php + means integer arithmetic. So change + to . (dot)<br />
<br />
=== On php side my string comparison does not work ===<br />
<br />
if ($color == '4baae2' || $color == '000000') { <br />
}<br />
<br />
Apparently you should not be using '==' in php to compare strings! You should use '==='. The (==) operator will typecast the strings <br />
to numbers then do comparison!<br />
Its not very apparent because usually you can get away with it, but not when strings resemble numbers like hex 'colors'.<br />
<br />
=== Integer columns in the database are returned as strings ===<br />
<br />
This is normal PHP behavior. All fields are returned as string type, regardless of their actual type in the database. This also applies to global variables accessed via getGameStateValue(), since they are stored in a database table.<br />
<br />
You can use type-casting to convert them to the correct type after they come out of the database.<br />
<br />
$myValueInt = (int)self::getGameStateValue(GLOBAL_ROUND_NUMBER);<br />
<br />
or<br />
<br />
$myValueInt = self::getGameStateValue(GLOBAL_ROUND_NUMBER) + 0; // adding 0 casts to int<br />
<br />
<br />
same with direct qdb ueries<br />
$sql = "SELECT my_int_column FROM my_table WHERE my_condition";<br />
$myResult = self::getUniqueValueFromDB($sql); <-- this is a string<br />
$myResultAsInt = (int)$myResult;<br />
<br />
https://stackoverflow.com/questions/5323146/mysql-integer-field-is-returned-as-string-in-php<br />
<br />
== Zombie mode ==<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: zombie): Not logged ===<br />
<br />
You are probably calling getCurrentPlayerId() or getCurrentPlayerName() in your zombieTurn method or any of the methods it uses. Instead, use the $active_player_id provided as parameter to zombieTurn().<br />
<br />
=== Unexpected error: Can't manage zombie player in this game state ===<br />
<br />
The state the game was in at the time the error was generated didn't have "transitions" => array( "zombiePass" => 27 )" (27 was an arbitrary example, but everything else must be spelled exactly that way.)<br />
<br />
To correct this error, either add "zombiePass" => ## to the transitions array or work out why the game is in a state it should not be in.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: zombie): ===<br />
<br />
This is almost certainly an undefined value in PHP code. Look for a warning or error message in the game replay log on the right side of the table UI.<br />
<br />
=== Unexpected error: BGA gameserver 1 do not respond (method: zombie) (timeout: cluster) ===<br />
<br />
You are trying to end the game from a zombie method. This is not allowed. The zombie logic must continue the game as best it can. See [[Main_game_logic:_yourgamename.game.php#Zombie_mode|Zombie mode]] for more info.<br />
<br />
=== Game unexpectedly ends when one player becomes zombie ===<br />
<br />
There is an error in handling zombie mode, check error log. Example: if the zombie turn is called again, framework detects a infinite loop and cancels the game. On the studio there is a notification called ZombieTurnFailed<br />
<br />
== Other errors ==<br />
<br />
=== Javascript does not know how to sum two numbers ===<br />
<br />
Be careful when you manipulate integers returned by notifications: most of the time, Javascript considers they are Strings and not Integers.<br />
<br />
As a result:<br />
<pre><br />
var i=1;<br />
i += notif.args.increment; // With notif.args.increment='1'<br />
alert( i ); // i=11 instead of 2 !! Javascript concatenate 2 strings !<br />
</pre><br />
<br />
To solve this, you should use the "toint" function:<br />
<pre><br />
var i=1;<br />
i += toint( notif.args.increment ); // With notif.args.increment='1'<br />
alert( i ); // i=2 :)<br />
</pre><br />
<br />
=== Javascript: do not use substr with negative numbers ===<br />
<br />
To get the last characters of a string, use "slice" instead of "substr" which has a bug on IE:<br />
<pre><br />
var three_last_characters = string.substr( -3 ); // Wrong<br />
var three_last_characters = string.slice( -3 ); // Correct<br />
</pre><br />
<br />
=== Game "spontaneously" transition to a new state without user input ===<br />
<br />
Make sure on php side you have no code after $this->gamestate->nextState(...) code.<br />
Because if you do accidentally have code that goes to another state it will cause another state transition without user interaction.<br />
<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
$this->gamestate->nextState ( 'last' ); // <-- here is missing else, so it will cause double state transition<br />
}</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Deck&diff=8545
Deck
2021-06-20T02:41:37Z
<p>JonChambers: /* Moving cards */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
"Deck" is one of the most useful component on the PHP side. With "Deck", you can manage the cards in your game on the server side.<br />
<br />
Using "deck", you will be able to use the following features without writing a single SQL database request:<br />
* Place cards in a pile, shuffle cards, draw cards one by one or many at a time.<br />
* "Auto-reshuffle" the discard pile into the deck when the deck is empty.<br />
* Move cards between different locations: hands of players, the table, etc.<br />
<br />
<br />
== Using Deck: Hearts example ==<br />
<br />
The Deck component is extensively used in the sample ''Hearts'' card game. You will find in "hearts.game.php" that the object "$this->cards" is used many times.<br />
<br />
== Deck overview ==<br />
<br />
With Deck component, you manage all cards of your game.<br />
<br />
=== The 5 properties of each card ===<br />
<br />
Using the Deck component, each card will have 5 properties:<br />
* '''id''': This is the unique ID of each card.<br />
* '''type''' and '''type_arg''': These two values define the type of your card (i.e., what sort of card is this?).<br />
* '''location''' and '''location_arg''': These two values define where the card is at now.<br />
<br />
The id, type, and type_arg properties are constants throughout the game. location and location_arg change when your cards move from one place to another in the game area.<br />
<br />
'''id''' is the unique ID of each card. Two cards cannot have the same ID. IDs are generated automatically by the Deck component when you create cards during the Setup phase of your game.<br />
<br />
'''type''' and '''type_arg''' defines the type of your card.<br />
<br />
'''type''' is a short string, and '''type_arg''' is an integer.<br />
<br />
You can use these two values as you like to make sure you will be able to identify the different cards in the game. See usage of "type" and "type_arg" below.<br />
<br />
Examples of usage of "type" and "type_arg":<br />
* In ''Hearts'', "type" represents the color (suite) of the card (1 to 4) and "type_arg" is the value of the card (1, 2, ... 10, J, Q, K).<br />
* In ''Seasons'', "type" represents the type of the card (e.g., 1 is Amulet of Air, 2 is Amulet of Fire, etc...). type_arg is not used.<br />
* In ''Takenoko'', a Deck component is used for objective cards. "type" is the kind of objective (irrigation/panda/plot) and "type_arg" is the ID of the specific objective to realize (e.g., "green bamboo x4"). Note that a second Deck component is used in ''Takenoko'' to manage the "garden plot" pile.<br />
<br />
'''location''' and '''location_arg''' define where a card is at now. '''location''' is a short string, and '''location_arg''' is an integer.<br />
<br />
You can use 'location' and 'location_arg' as you like, to move your card within the game area.<br />
<br />
There are 3 special 'location' values that Deck manages automatically. You can choose to use these locations or not, depending on your needs:<br />
* 'deck': the 'deck' location is a standard draw deck. Cards are placed face down in a stack and are drawn in sequential order during the game. 'location_arg' is used to specify where the card is located within the stack (the card with the highest location_arg value is the next to be drawn).<br />
* 'hand': the 'hand' location represents cards in a player's hand. 'location_arg' is set to the ID of each player.<br />
* 'discard': the 'discard' location is used for discard piles. Card in 'discard' may be reshuffled into the deck if needed (see "autoreshuffle").<br />
<br />
<br />
Tips: using the Deck component, you will use generic properties ("location", "type_arg",...) for specific purposes in your game. Thus, during the design step before realizing your game, take a few minutes to write down the exact meaning of each of these generic properties in the context of your game.<br />
<br />
=== Create a new Deck component ===<br />
<br />
For each Deck component in your game, you need to create a dedicated table in the SQL database. This table has a standard format. In practice, if you just want to have a Deck component named "card", you can copy/paste the following into your "dbmodel.sql" file:<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
Note: the database schema of this table does not have to be exactly what is listed above. You can increase the size of the fields or add more fields. For additional fields<br />
you just have to do manual queries.<br />
<br />
Once you have done this (and restarted your game), you can declare the Deck component in your PHP code in your class constructor. For ''Hearts'' for example, I added to the "Hearts()" method:<br />
<br />
<pre><br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Note that we specify "card" here: the name of our previously created table. This means you can create several "Deck" components with multiple tables:<br />
<br />
<pre><br />
$this->firstKindCards = self::getNew( "module.common.deck" );<br />
$this->firstKindCards ->init( "first_kind_card" );<br />
$this->secondKindCards = self::getNew( "module.common.deck" );<br />
$this->secondKindCards ->init( "second_kind_card" );<br />
</pre><br />
<br />
Most of the time this is not useful; a Deck component should manage all objects of the same kind (i.e., all cards in the game).<br />
Note that you need to create a table for each "Deck", table name should be "first_kind_card" but the fields must remain "card_id", "card_type" and so on.<br />
<br />
Afterwards, we can initialize your "Deck" by creating all the cards of the game. Generally, this is done only once during the game, in the "setupNewGame" method.<br />
<br />
The "Deck" component provides a fast way to initialize all your cards at once: createCards. Here is how it is used for "Hearts":<br />
<pre><br />
// Create cards<br />
$cards = array();<br />
foreach( $this->colors as $color_id => $color ) // spade, heart, diamond, club<br />
{<br />
for( $value=2; $value<=14; $value++ ) // 2, 3, 4, ... K, A<br />
{<br />
$cards[] = array( 'type' => $color_id, 'type_arg' => $value, 'nbr' => 1);<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
As you can see, "createCards" takes a description of all cards of the game. For each type of card, you have to specify its "type", "type_arg" and the number of cards to create ("nbr"). "createCards" create all cards and place them into the "deck" location (as specified in the second argument).<br />
<br />
Now, you are ready to use "Deck"!<br />
<br />
=== Simple examples using Deck ===<br />
<br />
(Most examples are from "Hearts" game)<br />
<br />
<pre><br />
// In "getAllDatas', we need to send to the current player all the cards he has in hand:<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $player_id );<br />
</pre><br />
<br />
<pre><br />
// At some time we want to check if all the cards (52) are in player's hands:<br />
if( $this->cards->countCardInLocation( 'hand' ) == 52 )<br />
// do something<br />
</pre><br />
<br />
<pre><br />
// When a player plays a card in front of him on the table:<br />
$this->cards->moveCard( $card_id, 'cardsontable', $player_id );<br />
<br />
// Note the use of the custom location 'cardsontable' here to keep track of cards on the table.<br />
</pre><br />
<br />
<br />
<pre><br />
// This is a new hand: let's gather all cards from everywhere in the deck:<br />
$this->cards->moveAllCardsInLocation( null, "deck" );<br />
<br />
// And then shuffle the deck<br />
$this->cards->shuffle( 'deck' );<br />
<br />
// And then deal 13 cards to each player<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach( $players as $player_id => $player )<br />
{<br />
$cards = $this->cards->pickCards( 13, 'deck', $player_id );<br />
<br />
// Notify player about his cards<br />
self::notifyPlayer( $player_id, 'newHand', '', array( <br />
'cards' => $cards<br />
) );<br />
} <br />
<br />
// Note the use of "notifyPlayer" instead of "notifyAllPlayers": new cards is a private information ;) <br />
</pre><br />
<br />
== Deck component reference ==<br />
<br />
=== Initializing Deck component ===<br />
<br />
'''init( $table_name )'''<br />
<br />
Initialize the Deck component.<br />
<br />
Argument:<br />
* table_name: name of the DB table used by this Deck component.<br />
<br />
Must be called before any other Deck method.<br />
<br />
Usually, init is called in your game constructor.<br />
<br />
Example with Hearts:<br />
<br />
<pre><br />
function Hearts( )<br />
{<br />
(...)<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
}<br />
</pre> <br />
<br />
'''createCards( $cards, $location='deck', $location_arg=null )'''<br />
<br />
Create card items in your deck component. Usually, all card items are created once, during the setup phase of the game.<br />
<br />
"cards" describe all cards that need to be created. "cards" is an array with the following format:<br />
<br />
<pre><br />
// Create 1 card of type "1" with type_arg=99,<br />
// and 4 cards of type "2" with type_arg=12,<br />
// and 2 cards of type "3" with type_arg=33<br />
<br />
$cards = array(<br />
array( 'type' => 1, 'type_arg' => 99, 'nbr' => 1 ),<br />
array( 'type' => 2, 'type_arg' => 12, 'nbr' => 4 ),<br />
array( 'type' => 3, 'type_arg' => 33, 'nbr' => 2 )<br />
...<br />
);<br />
</pre><br />
<br />
Note: During the "createCards" process, Deck generates unique IDs for all card items.<br />
<br />
Note: createCards is optimized to create a lot of cards at once. Do not use it to create cards one by one.<br />
<br />
If "location" and "location_arg" arguments are not set, newly created cards are placed in the "deck" location. If "location" (and optionally location_arg) is specified, cards are created for this specific location.<br />
<br />
=== Card standard format ===<br />
<br />
When Deck component methods are returning one or several cards, the following format is used:<br />
<br />
<pre><br />
array(<br />
'id' => .., // the card ID<br />
'type' => .., // the card type<br />
'type_arg' => .., // the card type argument<br />
'location' => .., // the card location<br />
'location_arg' => .. // the card location argument<br />
);<br />
</pre><br />
<br />
=== Picking cards ===<br />
<br />
'''pickCard( $location, $player_id )'''<br />
<br />
Pick a card from a "pile" location (ex: "deck") and place it in the "hand" of specified player.<br />
<br />
Return the card picked or "null" if there are no more card in given location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCards( $nbr, $location, $player_id )'''<br />
<br />
Pick "$nbr" cards from a "pile" location (ex: "deck") and place them in the "hand" of specified player.<br />
<br />
Return an array with the cards picked (indexed by the card ID), or "null" if there are no more card in given location.<br />
<br />
Note that the number of cards picked can be less than "$nbr" in case there are not enough cards in the pile location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below). In case there are not enough cards in the pile, all remaining cards are picked first, then the auto-reshuffle is triggered, then the other cards are picked.<br />
<br />
'''pickCardForLocation( $from_location, $to_location, $location_arg=0 )'''<br />
<br />
This method is similar to 'pickCard', except that you can pick a card for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking a card.<br />
* to_location is the location where you will place the card picked.<br />
* if "location_arg" is specified, the card picked will be set with this "location_arg".<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCardsForLocation( $nbr, $from_location, $to_location, $location_arg=0, $no_deck_reform=false )'''<br />
<br />
This method is similar to 'pickCards', except that you can pick cards for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking some cards.<br />
* to_location is the location where you will place the cards picked.<br />
* if "location_arg" is specified, the cards picked will be set with this "location_arg".<br />
* if "no_deck_reform" is set to "true", the auto-reshuffle feature is disabled during this method call.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
=== Moving cards ===<br />
<br />
'''moveCard( $card_id, $location, $location_arg=0 )'''<br />
<br />
Move the specific card to given location.<br />
<br />
* card_id: ID of the card to move.<br />
* location: location where to move the card.<br />
* location_arg: if specified, location_arg where to move the card. If not specified "location_arg" will be set to 0.<br />
<br />
<br />
'''moveCards( $cards, $location, $location_arg=0 )'''<br />
<br />
Move the specific cards to given location.<br />
<br />
* cards: an array of IDs of cards to move.<br />
* location: location where to move the cards.<br />
* location_arg: if specified, location_arg where to move the cards. If not specified "location_arg" will be set to 0.<br />
<br />
'''insertCard( $card_id, $location, $location_arg )'''<br />
<br />
Move a card to a specific "pile" location where card are ordered.<br />
<br />
If location_arg place is already taken, increment all cards after location_arg in order to insert new card at this precise location.<br />
<br />
(note: insertCardOnExtremePosition method below is more useful in most of the case)<br />
<br />
'''insertCardOnExtremePosition( $card_id, $location, $bOnTop )'''<br />
<br />
Move a card on top or at bottom of given "pile" type location. (Lower numbers: bottom of the deck. Higher numbers: top of the deck.)<br />
<br />
(note: Filling an empty location this way with N cards creates "location_arg"s from 1 to N if "$bOnTop" is true and -1 to -N if "$bOnTop" is false. This can cause off-by-one errors for code intended to run on a deck generated by "shuffle( $location )" which generates "location_arg"s from 0 to N - 1.)<br />
<br />
'''moveAllCardsInLocation( $from_location, $to_location, $from_location_arg=null, $to_location_arg=0 )'''<br />
<br />
Move all cards in specified "from" location to given location.<br />
<br />
* from_location: where to take the cards. If null, cards from all locations will be move.<br />
* to_location: where to put the cards<br />
* from_location_arg (optional): if specified, only cards with given "location_arg" are moved.<br />
* to_location_arg (optional): if specified, cards moved "location_arg" is set to given value. Otherwise "location_arg" is set to 0.<br />
<br />
Note: if you want to keep "location_arg" untouched, you should use "moveAllCardsInLocationKeepOrder" below.<br />
<br />
'''moveAllCardsInLocationKeepOrder( $from_location, $to_location )'''<br />
<br />
Move all cards in specified "from" location to given "to" location. This method does not modify the "location_arg" of cards.<br />
<br />
'''playCard( $card_id )'''<br />
<br />
Move specified card at the top of the "discard" location.<br />
<br />
Note: this is an alias for: insertCardOnExtremePosition( $card_id, "discard", true )<br />
<br />
=== Get cards informations ===<br />
<br />
'''getCard( $card_id )'''<br />
<br />
Get specific card information.<br />
<br />
Return null if this card is not found.<br />
<br />
'''getCards( $cards_array )'''<br />
<br />
Get specific cards information.<br />
<br />
$cards_array is an array of card IDs.<br />
<br />
If some cards are not found or if some card IDs are specified multiple times, the method throws an (unexpected) Exception.<br />
<br />
'''getCardsInLocation( $location, $location_arg = null, $order_by = null )'''<br />
<br />
Get all cards in specific location, as an array. Return an empty array if the location is empty.<br />
<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
* order_by (optional): if specified, returned cards are ordered by the given database field. Example: "card_id" or "card_type".<br />
<br />
'''countCardInLocation( $location, $location_arg=null )'''<br />
<br />
Return the number of cards in specified location.<br />
<br />
* location (string): the location where to count the cards.<br />
* location_arg (optional): if specified, count only cards with the specified "location_arg".<br />
<br />
'''countCardsInLocations()'''<br />
<br />
Return the number of cards in each location of the game.<br />
<br />
The method returns an associative array with the format "location" => "number of cards".<br />
<br />
Example:<br />
<pre><br />
array(<br />
'deck' => 12,<br />
'hand' => 21,<br />
'discard' => 54,<br />
'ontable' => 3<br />
);<br />
</pre><br />
<br />
'''countCardsByLocationArgs( $location )'''<br />
<br />
Return the number of cards in each "location_arg" for the given location.<br />
<br />
The method returns an associative array with the format "location_arg" => "number of cards".<br />
<br />
Example: count the number of cards in each player's hand:<br />
<pre><br />
countCardsByLocationArgs( 'hand' );<br />
<br />
// Result:<br />
array(<br />
122345 => 5, // player 122345 has 5 cards in hand<br />
123456 => 4 // and player 123456 has 4 cards in hand<br />
);<br />
</pre><br />
<br />
<br />
'''getPlayerHand( $player_id )'''<br />
<br />
Get all cards in given player hand.<br />
<br />
Note: This is an alias for:<br />
getCardsInLocation( "hand", $player_id )<br />
<br />
'''getCardOnTop( $location )'''<br />
<br />
Get the card on top of the given ("pile" style) location, or null if the location is empty.<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is no more card available.<br />
<br />
'''getCardsOnTop( $nbr, $location )'''<br />
<br />
Get the "$nbr" cards on top of the given ("pile" style) location.<br />
<br />
The method return an array with at most "$nbr" elements (or a void array if there is no card in this location).<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is not enough cards available.<br />
<br />
'''getExtremePosition( $bGetMax ,$location )'''<br />
<br />
(rarely used)<br />
<br />
Get the position of cards at the top of the given location / at the bottom of the given location.<br />
<br />
Of course this method works only on location in "pile" where you are using "location_arg" to specify the position of each card (example: "deck" location).<br />
<br />
If bGetMax=true, return the location of the top card of the pile.<br />
<br />
If bGetMax=false, return the location of the bottom card of the pile.<br />
<br />
'''getCardsOfType( $type, $type_arg=null )'''<br />
<br />
Get all cards of a specific type (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
<br />
'''getCardsOfTypeInLocation( $type, $type_arg=null, $location, $location_arg = null )<br />
<br />
Get all cards of a specific type in a specific location (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
<br />
=== Shuffling ===<br />
<br />
'''shuffle( $location )'''<br />
<br />
Shuffle all cards in specific location.<br />
<br />
Shuffle only works on locations where cards are on a "pile" (ex: "deck").<br />
<br />
Please note that all "location_arg" will be reset to reflect the new order of the cards in the pile.<br />
<br />
=== Auto-reshuffle ===<br />
<br />
To enable auto-reshuffle you must do "$this->cards->autoreshuffle = true" during the setup of the component (often in the ''_construct'' function when you ''init()'' the Deck object).<br />
<br />
Every time a card must be retrieved from the "deck" location, if it is empty the "discard" location will be automatically reshuffled into the "deck" location.<br />
<br />
If you need to notify players when the deck is shuffled, you can setup a callback method using this feature: $this->cards->autoreshuffle_trigger = array('obj' => $this, 'method' => 'deckAutoReshuffle');<br />
<br />
If you need to use other locations than "deck" and "discard" for auto-reshuffle feature, you can configure it this way: $this->cards->autoreshuffle_custom = array('deck' => 'discard'); (replace 'deck' and 'discard' with your custom locations).</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Your_game_state_machine:_states.inc.php&diff=8482
Your game state machine: states.inc.php
2021-06-09T01:55:25Z
<p>JonChambers: /* Complete examples */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This file describes the state machine of your game (all the game states properties, and the transitions to get from one state to another).<br />
<br />
Important: to understand the game state machine, it's recommended that you read this presentation first:<br />
<br />
[http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine]<br />
<br />
== Overall structure ==<br />
<br />
The machine states are described by a PHP associative array.<br />
<br />
Example:<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
// The initial state. Please do not modify.<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 2 )<br />
),<br />
<br />
// Note: ID=2 => your first state<br />
<br />
2 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a card or pass'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a card or pass'),<br />
"type" => "activeplayer",<br />
"possibleactions" => array( "playCard", "pass" ),<br />
"transitions" => array( "playCard" => 2, "pass" => 2 )<br />
),<br />
</pre><br />
<br />
== Syntax ==<br />
<br />
=== id ===<br />
<br />
The keys determine game state IDs (in the example above: 1 and 2).<br />
<br />
IDs must be positive integers.<br />
<br />
ID=1 is reserved for the first game state and should not be used (and you must not modify it).<br />
<br />
ID=99 is reserved for the last game state (end of the game) (and you must not modify it).<br />
<br />
Note: you may use any ID, even an ID greater than 100. But you cannot use 1 or 99.<br />
<br />
Note²: You must not use the same ID twice.<br />
<br />
Note³: When a game is in prod and you change the ID of a state, all active games (including many turn based) will behave unpredictably.<br />
<br />
=== name ===<br />
<br />
('''Mandatory''')<br />
<br />
The name of a game state is used to identify it in your game logic.<br />
<br />
Several game states can share the same name; however, this is not recommended.<br />
<br />
Warning! Do not put spaces in the name. This could cause unexpected problems in some cases.<br />
<br />
PHP example:<br />
<pre><br />
<br />
// Get current game state<br />
$state = $this->gamestate->state();<br />
if( $state['name'] == 'myGameState' )<br />
{<br />
...<br />
}<br />
<br />
</pre><br />
<br />
JS example:<br />
<pre><br />
onEnteringState: function( stateName, args )<br />
{<br />
console.log( 'Entering state: '+stateName );<br />
<br />
switch( stateName )<br />
case 'myGameState':<br />
<br />
// Do some stuff at the beginning at this game state<br />
....<br />
<br />
break;<br />
</pre><br />
<br />
=== type ===<br />
<br />
('''Mandatory''')<br />
<br />
You can use 3 types of game states:<br />
* activeplayer (1 player is active and must play.)<br />
* multipleactiveplayer (1..N players can be active and must play.)<br />
* game (No player is active. This is a transitional state to do something automatic specified by the game rules.)<br />
<br />
'''Note:''' Make sure you don't mistype the value of this attribute. If you do (e.g. 'multiactiveplayer' instead of 'multipleactiveplayer'), things won't work, and you might have a hard time figuring out why.<br />
<br />
=== description ===<br />
<br />
('''Mandatory''')<br />
<br />
The description is the string that is displayed in the main action bar (top of the screen) when the state is active.<br />
<br />
When a string is specified as a description, you must use "clienttranslate" in order for the string to be translated on the client side:<br />
<br />
<pre><br />
"description" => clienttranslate('${actplayer} must play a card or pass'),<br />
</pre><br />
<br />
In the description string, you can use ${actplayer} to refer to the active player.<br />
<br />
You can also use custom arguments in your description. These custom arguments correspond to values returned by your "args" PHP method (see below "args" field).<br />
<br />
Example of custom field:<br />
<pre><br />
<br />
In states.inc.php:<br />
"description" => clienttranslate('${actplayer} must choose ${nbr} identical energies'),<br />
"args" => "argMyArgumentMethod"<br />
<br />
In mygame.game.php:<br />
function argMyArgumentMethod()<br />
{<br />
return array(<br />
'nbr' => 2 // In this case ${nbr} in the description will be replaced by "2"<br />
); <br />
}<br />
</pre><br />
<br />
Note: You may specify an empty string ("") here if it never happens that the game remains in this state (i.e., if this state immediately jumps to another state when activated).<br />
<br />
Note²: Usually, you specify a string for "activeplayer" and "multipleactiveplayer" game states, and you specify an empty string for "game" game states. BUT, if you are using synchronous notifications, the client can remain on a "game" type game state for a few seconds, and in this case it may be useful to display a description in the status bar while in this state.<br />
<br />
=== descriptionmyturn ===<br />
<br />
('''Mandatory''' when the state type is "activeplayer" or "multipleactiveplayer")<br />
<br />
"descriptionmyturn" has exactly the same role and properties as "description", except that this value is displayed to the current active player - or to all active players in case of a multipleactiveplayer game state.<br />
<br />
In general, we have this situation:<br />
<br />
<pre><br />
"description" => clienttranslate('${actplayer} can take some actions'),<br />
"descriptionmyturn" => clienttranslate('${you} can take some actions'),<br />
</pre><br />
<br />
Note: you can use ${you} in descriptionmyturn so the description will display "You" instead of the name of the player.<br />
<br />
Note 2: you can use ${otherplayer} to refer to some other player if you want this to be shown in player's color, but you must provide otherplayer_id as argument (along with otherplayer) to specify this player's id in this case or game won't load.<br />
<br />
I.e.<br />
<br />
"descriptionmyturn" => clienttranslate('${you} can follow action of ${otherplayer}'),<br />
<br />
And this will have to be state arguments for this<br />
<pre><br />
function arg_playerTurnFollow(){<br />
return ['otherplayer'=> $this->getLeaderPlayerName(),<br />
'otherplayer_id'=> $this->getLeaderPlayerId()<br />
];<br />
}<br />
</pre><br />
<br />
=== action ===<br />
<br />
('''Mandatory''' when the state type is "game.")<br />
<br />
"action" specifies a PHP method to call when entering this game state.<br />
<br />
Example:<br />
In states.inc.php:<br />
<pre><br />
28 => array(<br />
"name" => "gameTurnNextPlayer",<br />
"description" => clienttranslate('Updating some stuff...'),<br />
"type" => "game",<br />
"action" => "st_gameTurnNextPlayer",<br />
</pre><br />
In mygame.game.php:<br />
<pre><br />
function st_gameTurnNextPlayer() {<br />
$player_id = $this->getActivePlayerId();<br />
$next_player_id = $this->getPlayerAfter($player_id);<br />
$this->giveExtraTime($next_player_id);<br />
$this->incStat(1, 'turns_number', $next_player_id);<br />
$this->incStat(1, 'turns_number');<br />
$this->gamestate->changeActivePlayer($next_player_id);<br />
$this->gamestate->nextState('next');<br />
}<br />
</pre><br />
<br />
Usually, for a "game" state type, the action method is used to perform automatic functions specified by the rules (for example: check victory conditions, deal cards for a new round, go to the next player, etc.) and then jump to another game state.<br />
<br />
Note: a BGA convention specifies that PHP methods called with "action" are prefixed by "st".<br />
<br />
Note: this field CAN be used for player states to set something up; e.g., for multiplayer states, it can make all players active.<br />
<br />
Example for action in player state:<br />
in states.inc.php:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'st_MultiPlayerInit',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "playPlace" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
in mygame.game.php:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
<br />
=== transitions ===<br />
<br />
('''Mandatory''')<br />
<br />
With "transitions" you specify which game state(s) you can jump to from a given game state.<br />
<br />
Example:<br />
<pre><br />
25 => array(<br />
"name" => "myGameState",<br />
"transitions" => array( "nextPlayer" => 27, "endRound" => 39 ),<br />
....<br />
}<br />
</pre><br />
<br />
In the example above, if "myGameState" is the current active game state, I can jump to the game state with ID 27 or the game state with ID 39.<br />
<br />
Example to jump to ID 27:<br />
<pre><br />
In mygame.game.php:<br />
$this->gamestate->nextState( "nextPlayer" );<br />
</pre><br />
<br />
Important: "nextPlayer" is the name of the transition, and NOT the name of the target game state. Multiple transitions can lead to the same game state.<br />
<br />
Note: If there is only 1 transition, you may give it an empty name.<br />
<br />
Example:<br />
<pre><br />
In states.inc.php:<br />
"transitions" => array( "" => 27 ),<br />
<br />
In mygame.game.php:<br />
$this->gamestate->nextState( ); // We don't need to specify a transition as there is only one here<br />
</pre><br />
<br />
=== possibleactions ===<br />
<br />
('''Mandatory''' when the game state is "activeplayer" or "multipleactiveplayer")<br />
<br />
"possibleactions" defines the actions possible by the players in this game state, and ensures they cannot perform actions that are not allowed in this state.<br />
<br />
Example:<br />
<br />
<pre><br />
In states.game.php:<br />
"possibleactions" => array( "playCard", "pass" ),<br />
<br />
In mygame.game.php:<br />
function playCard( ...)<br />
{<br />
self::checkAction( "playCard" ); // Will fail if "playCard" is not specified in "possibleactions" in the current game state.<br />
<br />
....<br />
<br />
In mygame.js:<br />
playCard: function( ... )<br />
{<br />
if( this.checkAction( "playCard" ) ) // Will fail if "playCard" is not specified in "possibleactions" in the current game state.<br />
{ return ; }<br />
<br />
....<br />
<br />
</pre><br />
<br />
=== args ===<br />
<br />
(optional)<br />
<br />
Sometimes it happens that you need some information on the client side (i.e., for your game interface) only for a specific game state.<br />
<br />
Example 1: in ''Reversi'', the list of possible moves during the playerTurn state.<br />
<br />
Example 2: in ''Caylus'', the number of remaining king's favors to choose in the state where the player is choosing a favor.<br />
<br />
Example 3: in ''Can't Stop'', the list of possible die combinations to be displayed to the active player so that he can choose from among them.<br />
<br />
In such a situation, you can specify a method name as the « args » argument for your game state. This method must retrieve some piece of information about the game (example: for ''Reversi'', the list of possible moves) and return it.<br />
<br />
Thus, this data can be transmitted to the clients and used by the clients to display it. It should always be an associative array.<br />
<br />
Let's see a complete example using args with ''Reversi'' game:<br />
<br />
In states.inc.php, we specify an '''args''' argument for gamestate '''playerTurn''':<br />
<pre><br />
10 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a disc'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a disc'),<br />
"type" => "activeplayer",<br />
"args" => "argPlayerTurn", // <==== HERE<br />
"possibleactions" => array( 'playDisc' ),<br />
"transitions" => array( "playDisc" => 11, "zombiePass" => 11 )<br />
),<br />
</pre><br />
<br />
It corresponds to a '''argPlayerTurn''' method in our PHP code (reversi.game.php):<br />
<pre><br />
function argPlayerTurn() {<br />
return array(<br />
'possibleMoves' => self::getPossibleMoves()<br />
);<br />
}<br />
</pre><br />
<br />
Then, when we enter into the '''playerTurn''' game state on the client side, we can highlight the possible moves on the board using information returned by '''argPlayerTurn''':<br />
<br />
<pre><br />
onEnteringState: function( stateName, args ) {<br />
console.log( 'Entering state: '+stateName );<br />
<br />
switch( stateName ) {<br />
case 'playerTurn':<br />
this.updatePossibleMoves( args.args.possibleMoves );<br />
break;<br />
}<br />
},<br />
</pre><br />
<br />
==== Naming and API conventions ====<br />
<br />
As a BGA convention, PHP methods called with "args" are prefixed by "arg" followed by state name (example: '''argPlayerTurn''').<br />
<br />
All "arg*" methods are put into corresponding section of .game.php file commented as<br />
//////////// Game state arguments<br />
<br />
Arg method MUST return array. Return integer or string will result in some undebuggable exceptions.<br />
<br />
Arg method MUST be defined in php if it is declared in state description, if you don't need it comment it out from the states.inc.php file (the '''args''' parameter). If you not sure if you need it or not you can keep it returning empty array until you figure it out.<br />
<br />
function argPlayerTurn() {<br />
return array(); // must be an array<br />
}<br />
<br />
<br />
'''Warning''': the "args" method can be called before the "action" method so don't expect data modifications by the "action" method to be available in the "args" method!<br />
Also don't modify database in this method!<br />
<br />
You should NOT be calling getCurrentPlayer() in the context of this function, since state transitions are broadcasted to all player independent of who initiated it. Instead you should send information on per player basis (Note: if this is private info see section below). <br />
<br />
If you using this function in multi-player state, you should not call getActivePlayer() either, you should send per player info:<br />
<br />
function arg_playerTurn() {<br />
$res = array ();<br />
$players = $this->loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player_info ) {<br />
$color = $player_info ['player_color'];<br />
$res [$player_id] = array("color"=>$color);<br />
}<br />
return $res;<br />
}<br />
<br />
Note: you never need to send player color like this, this is just an example.<br />
<br />
Note 2: you CAN call methods that return all active players if multi-player states if its relevant.<br />
<br />
==== Other usages ====<br />
<br />
You can use values returned by your "args" method to have some custom values in your "description"/"descriptionmyturn", i.e. in states.inc.php:<br />
<br />
"descriptionmyturn" => clienttranslate('${you} must play ${color} disc'),<br />
<br />
So arg function will be something like:<br />
<br />
function argPlayerTurn() {<br />
return array('color'=>this->getActivePlayerColor()); <br />
}<br />
<br />
You can use args also in '''onUpdateActionButtons''' function on js side, however two important notes:<br />
* it is just '''args''' (not args.args like in '''onEnteringState''')<br />
* it is args of the current state, which may not be state you think it will be! This method is called during change of active player, which happens in some weird place especially during "multipleactiveplayer" states. If you pulling your hair thinking why its "undefined" on js side - check the current state.<br />
<br />
==== Private info in args ====<br />
<br />
By default, all data provided through this method are PUBLIC TO ALL PLAYERS. Please do not send any private data with this method, as a cheater could see it even it is not used explicitly by the game interface logic.<br />
<br />
However, it is possible to specify that some data should be sent to specific players only.<br />
<br />
Example 1: send information to active player(s) only:<br />
<br />
<pre><br />
function argPlayerTurn() {<br />
return array(<br />
'_private' => array( // Using "_private" keyword, all data inside this array will be made private<br />
'active' => array( // Using "active" keyword inside "_private", you select active player(s)<br />
'somePrivateData' => self::getSomePrivateData() // will be send only to active player(s)<br />
)<br />
),<br />
'possibleMoves' => self::getPossibleMoves() // will be sent to all players<br />
);<br />
}<br />
</pre><br />
<br />
Inside the js file, these variables will be available through `args.args._private`. (e.g. `args.args._private.somePrivateData` -- it is not `args._private.active.somePrivateData` nor is it `args.somePrivateData`)<br />
<br />
Example 2: send information to a specific player (<specific_player_id>) only:<br />
<br />
<pre><br />
function argPlayerTurn() {<br />
$specific_player_id = ...; // calculate some-how<br />
return array(<br />
'_private' => array( // all data inside this array will be private<br />
$specific_player_id => array( // will be sent only to that player <br />
'somePrivateData' => self::getSomePrivateData() <br />
)<br />
),<br />
<br />
'possibleMoves' => self::getPossibleMoves() // will be sent to all players<br />
);<br />
}<br />
</pre><br />
<br />
IMPORTANT: in certain situations (i.e. "multipleactiveplayer" game state) these "private data" features can have a significant impact on performance. Please do not use if not needed.<br />
<br />
It is also possible to use these private args in the "description" messages, like "${you} have to play ${_private.count} cards".<br />
<br />
<br />
==== Translations in args ====<br />
You can do the same as in notification by adding i18n parameter, i.e<br />
<pre><br />
function argPlayerTurn() {<br />
return [<br />
'i18n' => ['terrainName' ],<br />
'terrainName' => ($this->token_types[$terrain]['name']), // this should be defined in material.inc.php and with clienttranslate <br />
'terrain' => $terrain,<br />
];<br />
}<br />
</pre><br />
<br />
Than you can do something like this in state:<br />
"descriptionmyturn" => clienttranslate('${you} must place a house on ${terrainName}'),<br />
<br />
See more in [[Translations]]<br />
<br />
=== updateGameProgression ===<br />
<br />
(optional)<br />
<br />
If you specify "updateGameProgression => true" in a game state, your "getGameProgression" PHP method will be called at the beginning of this game state - and thus the game progression of the game will be updated.<br />
<br />
''At least one'' of your game states (any one) must specify "updateGameProgression=>true".<br />
<br />
== Implementation Notes ==<br />
<br />
<br />
=== Using Named Constants for States ===<br />
<br />
Using numeric constants is prone to errors. If you want you can declare state constants as PHP named constants. This way you can<br />
use them in the states file and in game.php as well<br />
<br />
EXAMPLE:<br />
<br />
states.inc.php:<br />
<br />
<pre><br />
// define contants for state ids<br />
if (!defined('STATE_END_GAME')) { // ensure this block is only invoked once, since it is included multiple times<br />
define("STATE_PLAYER_TURN", 2);<br />
define("STATE_GAME_TURN", 3);<br />
define("STATE_PLAYER_TURN_CUBES", 4);<br />
define("STATE_END_GAME", 99);<br />
}<br />
<br />
$machinestates = array(<br />
<br />
...<br />
<br />
STATE_PLAYER_TURN => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must select an Action Space or Pass'),<br />
"descriptionmyturn" => clienttranslate('${you} must select an Action Space or Pass'),<br />
"type" => "activeplayer",<br />
"args" => 'arg_playerTurn',<br />
"possibleactions" => array( "selectWorkerAction", "pass" ),<br />
"transitions" => array( <br />
"loopback" => STATE_PLAYER_TURN,<br />
"playCubes" => STATE_PLAYER_TURN_CUBES,<br />
"pass" => STATE_GAME_TURN )<br />
),<br />
</pre><br />
<br />
=== Example of multipleactiveplayer state ===<br />
<br />
This is an example of a multipleactiveplayer state:<br />
<br />
2 => array (<br />
'name' => 'playerTurnSetup',<br />
'type' => 'multipleactiveplayer',<br />
'description' => clienttranslate('Other players must choose one Objective'),<br />
'descriptionmyturn' => clienttranslate('${you} must choose one Objective card to keep'),<br />
'possibleactions' => array ('playKeep' ),<br />
'transitions' => array ( 'next' => 5, 'loopback' => 2, ),<br />
'action' => 'st_MultiPlayerInit',<br />
'args' => 'arg_playerTurnSetup',<br />
),<br />
<br />
In game.php:<br />
// this will make all players multiactive just before entering the state<br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
<br />
Note: if you want exact this function its already defined in table class its called 'stMakeEveryoneActive'<br />
<br />
When ending the player action, instead of a state transition, deactivate player.<br />
<br />
function action_playKeep($cardId) {<br />
$this->checkAction('playKeep');<br />
$player_id = $this->getCurrentPlayerId(); // CURRENT!!! not active<br />
... // some logic here<br />
$this->gamestate->setPlayerNonMultiactive($player_id, 'next'); // deactivate player; if none left, transition to 'next' state<br />
}<br />
<br />
=== Diffrence between Single active and Multi active states ===<br />
In a classic "activePlayer" state:<br />
<br />
* You cannot change the active player DURING the state. This is to ensure that during 1 activePlayer state, only ONE player is active<br />
* As a consequence, you must set the active player BEFORE entering the activePlayer state<br />
* In such sates on JS side onUpdateActionButtons is called before onEnteringState during game play (but after during reload, i.e. F5)<br />
* Finally, during onEnteringState, on JS side, the active player is signaled as active and the information is reliable and usable.<br />
<br />
In a "multiplePlayer" state:<br />
<br />
* You can (and must) change the active players DURING the state<br />
* During such a state, players can be activated/desactivated anytime during the state, giving you the maximum of possibilities.<br />
* You shouldn't set active player before entering the state. But you can set it in "state initializer" php function (see example above st_MultiPlayerInit)<br />
* Finally, during onEnteringState, on JS side, the active players are NOT active yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/inactive status.<br />
<br />
=== Designing states ===<br />
<br />
As a general rule you state machine should resemble "round/turn overview" from the game rule-book. Normally if book say its player turn and player can do multiple things during their turn it is still only<br />
one player state (plus a game to switch player), not muliple states.<br />
<br />
In a classic game (i.e. chess), there is only active player at any time, so it is simple playerTurn/gameTurn sequence and you only need 2 states.<br />
<br />
[[File:Simplestates.png]]<br />
<br />
In complex euro game there can be multiple rounds, and each round have phases which can be distinctly unique, i.e. in first phase everybody draws card and discard (multi-player), then player have one turn each (single-active), then another is resolution of actions from players and some of them may become active again, then there is round upkeep/reset. This would require one multi-player state for phase 1, pair of states for phase2 (singel-active + game), pair of states for phase3 and finally round-end/unkeep game state.<br />
<br />
For pair of active player/game states you can make player state first, which transitions to game state, or the other way around, you start with game state which transition to active player state, its loop in any case but depends on how you want to do "phase" initiazations.<br />
<br />
[[File:Eurogamestates.png]]<br />
<br />
=== Complete examples ===<br />
<br />
Example of simple game where player take turns<br />
<br />
<pre><br />
if ( !defined('STATE_END_GAME')) { // guard since this included multiple times<br />
define("STATE_PLAYER_TURN", 2);<br />
define("STATE_GAME_TURN_NEXT_PLAYER", 3);<br />
define("STATE_PLAYER_GAME_END", 4);<br />
define("STATE_END_GAME", 99);<br />
}<br />
<br />
$machinestates = [ <br />
1 => [ // The initial state. Please do not modify.<br />
"name" => "gameSetup",<br />
"description" => "",<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => [ "" => STATE_PLAYER_TURN ] ],<br />
// Game states <br />
STATE_PLAYER_TURN => [ // main active player state <br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must do something or pass'),<br />
"descriptionmyturn" => clienttranslate('${you} must do something or pass'),<br />
"type" => "activeplayer",<br />
"args" => "arg_playerTurn",<br />
"possibleactions" => [ "dosomething","pass" ],<br />
"transitions" => [ "next" => STATE_GAME_TURN_NEXT_PLAYER,"last" => STATE_PLAYER_GAME_END ] // <br />
],<br />
STATE_GAME_TURN_NEXT_PLAYER => [ // next player state<br />
"name" => "gameTurnNextPlayer",<br />
"description" => clienttranslate('Upkeep...'),<br />
"type" => "game", //<br />
"action" => "st_gameTurnNextPlayer", //<br />
"updateGameProgression" => true,<br />
"transitions" => [ "next" => STATE_PLAYER_TURN,"loopback" => STATE_GAME_TURN_NEXT_PLAYER,<br />
"last" => STATE_PLAYER_GAME_END ], // TODO replace with STATE_END_GAME, its there to use undo/restore during dev<br />
],<br />
<br />
STATE_PLAYER_GAME_END => [ // active player state for debugging end of game<br />
"name" => "playerGameEnd",<br />
"description" => clienttranslate('${actplayer} Game Over'),<br />
"descriptionmyturn" => clienttranslate('${you} Game Over'),<br />
"type" => "activeplayer",<br />
"args" => "arg_playerTurn",<br />
"possibleactions" => ["endGame"],<br />
"transitions" => ["next" => STATE_END_GAME,"loopback" => STATE_PLAYER_GAME_END ] // <br />
],<br />
// End of Game states <br />
// Final state.<br />
// Please do not modify (and do not overload action/args methods).<br />
STATE_END_GAME => [<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd" ] <br />
<br />
];<br />
<br />
</pre></div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=8477
Game interface logic: yourgamename.js
2021-06-08T03:00:21Z
<p>JonChambers: /* Synchronous notifications */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
<br />
More details:<br />
<br />
;setup(gamedatas) <br />
This method must set up the game user interface according to current game situation specified in parameters.<br />
The method is called each time the game interface is displayed to a player, ie:<br />
<br />
- when the game starts<br />
<br />
- when a player refreshes the game page (F5)<br />
<br />
"gamedatas" argument contains all datas retrieved by your "getAllDatas" PHP method.<br />
<br />
;onEnteringState(stateName, args)<br />
This method is called each time we are entering into a new game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
To access state arguments passed via calling arg* method use args.args.<br />
Typically you would do something only for active player, using this.isCurrentPlayerActive() check.<br />
<br />
'''Warning''': for multipleactiveplayer states:<br />
the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.<br />
If you are doing initialization of some structure which do not depend on active player, you can just replace <br />
if (this.isCurrentPlayerActive()) {<br />
into<br />
if (!this.isSpectator) {<br />
for the main switch in that method.<br />
<br />
;onLeavingState(stateName)<br />
This method is called each time we are leaving a game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar.<br />
To access state arguments passed via calling arg* method use args parameter. Note: args can be null! For game states and when you don't supply state args function it is null.<br />
This method is called when active or multiactive player changes. In classic "activePlayer" state this method is called before the onEnteringState state.<br />
In multipleactiveplayer state it is a mess. The sequencing of call would depends on either you get into that from transition from reloading the whole game (i.e. F5).<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in replay mode <i>during the game</i> (the game is ongoing but the user clicked "reply from this move" in the log)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode <i>after the game</i> (the game has ended)<br />
<br />
; this.instantaneousMode<br />
: Returns true during replay/archive mode if animations should be skipped. Only needed if you are doing custom animations. (The BGA-provided animation functions like <i>this.slideToObject()</i> automatically handle instantaneous mode.)<br />
: Technically, when you click "replay from move #20", the system replays the game from the very beginning with moves 0 - 19 happening in instantaneous mode and moves 20+ happening in normal mode.<br />
<br />
---<br />
<br />
You may consider making a function like this, to detect if the game is in a read-only state:<br />
<br />
// Returns true for spectators, instant replay (during game), archive mode (after game end)<br />
isReadOnly: function () {<br />
return this.isSpectator || typeof g_replayFrom != 'undefined' || g_archive_mode;<br />
}<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of your javascript on the studio (and revert to the original).<br />
<br />
NB: it has been reported that there is an issue with this minifier and percentage values for opacity.<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You can use '''getElementById''' but a longer to type and less handy as it does not do some checks.<br />
Note²: It is safe to use if you don't know if variable is string (id of element) or element itself, i.e. <br />
<pre><br />
foo: function(card) {<br />
card = $(card); // now its node, no need to write if (typeof card === 'string') ...<br />
// but its good idea to check for null here<br />
...<br />
}<br />
</pre><br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
Note²: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
'''Vanila JS style'''<br />
$('my_element').style.display='none'; // set<br />
var display = $('my_element').style.display; // get<br />
$('my_element').style.removeProperty('display'); // remove<br />
<br />
'''dojo.addClass''', '''dojo.removeClass''', '''dojo.hasClass''', '''dojo.toggleClass'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.attr'''<br />
<br />
With dojo.attr you can access or change the value of an attribute or property of any HTML element in your interface.<br />
<br />
Exemple:<br />
<pre><br />
// Get the title of a node<br />
var title = dojo.attr( id, 'title' );<br />
// Change the height of a node<br />
dojo.attr( 'img_growing_tree', 'height', 100 );<br />
</pre><br />
<br />
'''Vanila JS attr'''<br />
<br />
$(token).id=new_id; // set attr for "id"<br />
var id = $(token).id; // get<br />
<br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''Vanila JS query'''<br />
<br />
var cards=document.querySelectorAll(".hand .card");// all cards in all hands<br />
var cards=$('hand').querySelectorAll(".card");// all cards in specific hand<br />
var card=document.querySelector(".hand .card");// first card or null if none (super handy)<br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positive integer: This parameter can be a positive integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place: [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
But you can also relocate elements like that. Note: it won't animate if you do that.<br />
<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
dojo.empty('my_hand');<br />
<br />
'''dojo.destroy'''<br />
<br />
Remove the element<br />
<br />
dojo.query(".green", mynode).forEach(dojo.destroy); // this remove all subnode of class green from mynode<br />
<br />
'''dojo.create'''<br />
<br />
Create element<br />
<br />
dojo.create("div", { class: "yellow_arrow" }, parent); // this creates div with class yellow_array and places it in "parent"<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' ).play();<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments, but it starts the animation. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it. Its starts the animation.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
Note: This wiki was probably written 10 years ago - now all modern broser support 'transform' so just ignore this and use transform.<br />
<br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
…<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
Example: associate a click on an element ("my_element") with one of our methods ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
Same idea but base on query (i.e. all element of 'pet' class)<br />
dojo.query(".pet").connect('onclick', this, 'onPet');<br />
<br />
Note: the methods described here are the only correct ways to associate a player input event to your code, and you should not use anything else.<br />
<br />
'''this.connect'''<br />
function(element, event, handler)<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
this.connect( $('my_element'), 'onclick', 'onClickOnMyElement' );<br />
<br />
Or you can use an in-place handler<br />
<br />
this.connect( $('my_element'), 'onclick', (e) => { console.log('boo'); } );<br />
<br />
Note that this function stores the connection handler. That is the only real difference between '''this.connect''' and '''dojo.connect'''. If you plan to destroy the element you connected, you '''must''' call this.disconnect() to prevent memory leaks.<br />
<br />
<br />
'''this.connectClass'''<br />
function( cssClassName, event, handler )<br />
<br />
Same as connect(), but for all the nodes set with the specified cssClassName.<br />
<pre><br />
this.connectClass('pet', 'onclick', 'onPet');<br />
</pre><br />
'''this.disconnect'''<br />
function( element, event )<br />
<br />
Disconnect event handler (previously registered with this.connect or this.connectClass).<br />
<br />
this.disconnect( $('my_element'), 'onclick');<br />
<br />
Note: dynamic connect/disconnect is for advanced cases ONLY, you should always connect elements statically if possible, i.e. in setup() method.<br />
<br />
'''this.disconnectAll'''<br />
<br />
Disconnect all previously registed event handlers (registered via this.connect or this.connectClass)<br />
<br />
'''this.checkAction'''<br />
function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
return true if action is authorized (i.e: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not authorized (display no message if nomessage parameter is true). <br />
The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt ) {<br />
if( this.checkAction( "my_action" ) ) {<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions'''<br />
function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server. '''It should not be triggered programmatically''', especially not in loops, in callbacks, in notifications, or in onEnteringState/onUpdateActionButtons/onLeavingState, in order not to create race conditions.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. <br />
** Note that "lock: true" must always be specified in this list of parameters in order the interface can be locked during the server call.<br />
** Note: Restricted parameter names (please don't use them):<br />
*** "action"<br />
*** "module"<br />
*** "class"<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine (not used, as all data handling is done via notifications).<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error. if no error this function is called with parameter value false.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
// NB : usually not needed as changes must be handled by notifications<br />
// You should NOT modify the interface in a callback or it will most likely break the framework replays (or make it inaccurate)<br />
// You should NOT make another ajaxcall in a callback in order not to create race conditions<br />
} );<br />
</pre><br />
<br />
Note: to reduce the boilerplate code you can define your own wrapper, which will do checking, locking and allow to skip parameters, for example<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) {<br />
args = [];<br />
}<br />
args.lock = true;<br />
<br />
if (this.checkAction(action)) {<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args,// <br />
this, (result) => { }, handler);<br />
}<br />
},<br />
</pre><br />
This can be called like this which is a lot more compact<br />
<pre><br />
this.ajaxcallwrapper('playDraw');<br />
this.ajaxcallwrapper('playMove', {card: id})<br />
</pre><br />
<br />
<br />
'''this.isInterfaceLocked()'''<br />
<br />
When using "lock: true" in ajax call you can use this function to check if interface is in lock state (it will be locked during server call and notification processing).<br />
This check can be used to block some other interactions which do not result in ajaxcall or if you want to suppress errors.<br />
<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<br />
PHP<br />
<br />
<pre><br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
</pre><br />
<br />
JavaScript<br />
<br />
<pre><br />
//You can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, there are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
-----<br />
<br />
It is also possible to control the delay timing dynamically (e.g., using notification args). As an example, maybe your notification 'cardPlayed' should pause for a different amount of time depending on the number or type of cards played.<br />
<br />
For this case, use '''setSynchronous''' without specifying the duration and use '''setSynchronousDuration''' within the notification callback.<br />
<br />
* NOTE: If you forget to invoke '''setSynchronousDuration''', the game will remain paused forever!<br />
<br />
<pre><br />
setupNotifications: function () {<br />
dojo.subscribe( 'cardPlayed', this, 'notif_cardPlayed' );<br />
this.notifqueue.setSynchronous( 'cardPlayed' ); // wait time is dynamic<br />
...<br />
},<br />
<br />
notif_cardPlayed: function (notif) {<br />
// MUST call setSynchronousDuration<br />
<br />
// Example 1: From notification args (PHP)<br />
this.notifqueue.setSynchronousDuration(notif.args.duration);<br />
...<br />
<br />
// Or, example 2: Match the duration to a Dojo animation<br />
var anim = dojo.fx.combine([<br />
...<br />
]);<br />
anim.play();<br />
this.notifqueue.setSynchronousDuration(anim.duration);<br />
},<br />
</pre><br />
<br />
=== Ignoring notifications ===<br />
Sometimes you need to ignore some notification on client side. You don't want them to be shown in game log and you don't want them to be handled.<br />
<br />
The most common use case is when a player gets private information. They will receive a specific notification (such as "You received Ace of Heart"), while other players would receive more generic notification ("Player received a card").<br />
<br />
In X.game.php<br />
<pre><br />
$this->notifyAllPlayers("dealCard", clienttranslate('${player_name} received a card'), [<br />
'player_id' => $playerId,<br />
'player_name' => $this->getActivePlayerName()<br />
]);<br />
<br />
$this->notifyPlayer($playerId, "dealCardPrivate", clienttranslate('You received ${cardName}'), [<br />
"type" => $card["type"],<br />
"cardName" => $this->getCardName($card["type"])<br />
]);<br />
</pre><br />
<br />
The problem with this approach is that the active player will receive two notifications:<br />
* Player1 received a card<br />
* You received Ace of Hearts<br />
<br />
Hence, notification ignoring. Similar to setting a synchronous notification above, you can set up a check whether a notification should be ignored:<br />
<pre><br />
this.notifqueue.setIgnoreNotificationCheck( 'dealCard', (notif) => (notif.args.player_id == this.player_id) );<br />
</pre><br />
<br />
'''setIgnoreNotificationCheck(notificationId, predicate)'''<br />
This method will set a check whether any of notifications of specific type should be ignored.<br />
<br />
The parameters are:<br />
* notificationId: before dispatching any notification of this type, the framework will call predicate to check whether notification should be ignored<br />
* predicate (notif => boolean): a function that will receive notif object and will return true if this specific notification should be ignored<br />
<br />
NOTE: You can think that it could be possible to send generic notification to all other players with notifyAllPlayers and it will seem to work. The problem is that table spectators would miss this notification and their user interface (and game log) wouldn't be updated.<br />
<br />
IMPORTANT: Remember that this notification is ignored on the client side, but was still received by the client. Therefore it shouldn't contain any private information as cheaters can get it. In other words this is not a way to hide information.<br />
<br />
IMPORTANT: When a game is reloaded with F5 or when opening a turn based game, old notifications are replayed as history notification. They are used just to update the game log and are stripped of all arguments except player_id, i18n and any argument present in message. If you use and other argument in your predicate you should preserve it as explained [[Main_game_logic:_yourgamename.game.php#Notify_players|here]].<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', [] );<br />
<br />
'''simplePause''' - This notification will just delay other notifications, maybe useful if you know you need some extra time for animation or something. Requires a time parameter.<br />
<br />
<pre><br />
self::notifyAllPlayers( 'simplePause', '', [ 'time' => 500] ); // time is in milliseconds<br />
</pre><br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening in the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfills one of the end of the game conditions, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of the current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info", "error", or "only_to_log". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background. If set to "only_to_log", the message will be added to the game log but will not popup at the top of the screen.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guideline of BGA is to AVOID the use of confirmation dialogs. Confirmation dialogs slow down the game and bother players. The players know that they have to pay attention to each move when they are playing online.<br />
<br />
The situations where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player does not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?'), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceCloseCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration, offset_x, offset_y );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
'''offset_x''' and '''offset_y''': if both offset_x and offset_y are defined and not null, apply the following offset (in pixels) to the scoring animation. Note that the score is centered in the anchor, so the offsets might have to be negative if you calculate the position.<br />
<br />
<br />
Note: if you want to display successively each score, you can use ''this.notifqueue.setSynchronous()'' function.<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble(anchor_id, text, delay, duration, custom_class)<br />
</pre><br />
<br />
text - what to put in bubble, can be html actually not just text<br />
<br />
delay - in milliseconds is optional (default 0)<br />
<br />
duration - in milliseconds is optional (default 3000)<br />
<br />
custom_class - extra class to add to bubble is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
Note: If you want this visually, but want to take complete control over this bubble and its animation (for example to make it permanent) you can just use div with 'discussion_bubble' class on it, and content of div is what will be shown.<br />
<br />
== Update players score ==<br />
<br />
The column player_score from the player table is automatically loaded into this.scoreCtrl and therefore into the stars location on the player board. This occurs sometime after the <gamename>.js setup() function. However this score must be updated as the game progresses through player notifications (notifs).<br />
<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
Typical usage would be (that will process 'score' notification):<br />
<pre><br />
setupNotifications : function() {<br />
...<br />
dojo.subscribe('score', this, "notif_score");<br />
},<br />
notif_score: function(notif) {<br />
this.scoreCtrl[notif.args.player_id].setValue(notif.args.player_score);<br />
},<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
NOTE: In modern JS there are lambdas that eliminate need for that, the example above will look like this<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), () => {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, (result) => {} );<br />
} ); <br />
</pre><br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
;'''onScreenWidthChange()'''<br />
:This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the :viewport size at the start of the game too.<br />
<br />
<br />
;'''updatePageTitle()'''<br />
:This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before :calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt ) {<br />
...<br />
if ( ... ) {<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
this.addActionButton( ... );<br />
...<br />
return;<br />
}<br />
...<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre><br />
<br />
== BGA Buttons ==<br />
<br />
You can create a custom button, but the BGA framework provides a standard button that requires only .css classes: '''bgabutton''' and '''bgabutton_${color}'''.<br />
<br />
'''Examples:'''<br />
<br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_blue"><span>My blue button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_gray"><span>My gray button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red"><span>My red button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red bgabutton_big"><span>My big red button</span></a><br />
</pre><br />
<br />
<br />
'''Note''': To see it in action, check out ''Coloretto''.<br />
<br />
<br />
'''Disabling:'''<br />
You can disable the '''bgabutton''' by adding the css class '''disabled''' in you js. The disabled button is still visible but is grey and not clickable.<br />
For example in the '''onUpdateActionButtons''' : <br />
<pre>this.addActionButton( 'play_button_id', _('Play 1 to 3 cards'), 'playFunctionButton', null, false, 'blue' ); //Create a blue button<br />
if (Condition == true)<br />
{<br />
dojo.addClass( 'play_button_id', 'disabled');//disable the button<br />
}</pre><br />
<br />
== Sounds ==<br />
<br />
Add a custom sound and make it load with your interface:<br />
<br />
Add this in your template (.tpl) file:<br />
<pre><br />
<audio id="audiosrc_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.mp3" preload="none" autobuffer></audio><br />
<audio id="audiosrc_o_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.ogg" preload="none" autobuffer></audio><br />
</pre><br />
<br />
Note: this is a requirement to provide both a mp3 and a ogg file.<br />
<br />
Play the sound (from your .js file):<br />
<br />
<pre><br />
playSound('<gamename>_<yoursoundname>'); <br />
</pre><br />
<br />
<br />
Disable the standard "move" sound for this move (to replace it with your custom sound):<br />
<br />
Add this to your notification handler:<br />
<br />
<pre><br />
this.disableNextMoveSound();<br />
</pre><br />
<br />
Note: it only disable the sound for the next move.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=8476
Game interface logic: yourgamename.js
2021-06-08T02:54:40Z
<p>JonChambers: If two languages are in the same box, it implies they are to be typed into the same file. Use different boxes for each.</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
<br />
More details:<br />
<br />
;setup(gamedatas) <br />
This method must set up the game user interface according to current game situation specified in parameters.<br />
The method is called each time the game interface is displayed to a player, ie:<br />
<br />
- when the game starts<br />
<br />
- when a player refreshes the game page (F5)<br />
<br />
"gamedatas" argument contains all datas retrieved by your "getAllDatas" PHP method.<br />
<br />
;onEnteringState(stateName, args)<br />
This method is called each time we are entering into a new game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
To access state arguments passed via calling arg* method use args.args.<br />
Typically you would do something only for active player, using this.isCurrentPlayerActive() check.<br />
<br />
'''Warning''': for multipleactiveplayer states:<br />
the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.<br />
If you are doing initialization of some structure which do not depend on active player, you can just replace <br />
if (this.isCurrentPlayerActive()) {<br />
into<br />
if (!this.isSpectator) {<br />
for the main switch in that method.<br />
<br />
;onLeavingState(stateName)<br />
This method is called each time we are leaving a game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar.<br />
To access state arguments passed via calling arg* method use args parameter. Note: args can be null! For game states and when you don't supply state args function it is null.<br />
This method is called when active or multiactive player changes. In classic "activePlayer" state this method is called before the onEnteringState state.<br />
In multipleactiveplayer state it is a mess. The sequencing of call would depends on either you get into that from transition from reloading the whole game (i.e. F5).<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in replay mode <i>during the game</i> (the game is ongoing but the user clicked "reply from this move" in the log)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode <i>after the game</i> (the game has ended)<br />
<br />
; this.instantaneousMode<br />
: Returns true during replay/archive mode if animations should be skipped. Only needed if you are doing custom animations. (The BGA-provided animation functions like <i>this.slideToObject()</i> automatically handle instantaneous mode.)<br />
: Technically, when you click "replay from move #20", the system replays the game from the very beginning with moves 0 - 19 happening in instantaneous mode and moves 20+ happening in normal mode.<br />
<br />
---<br />
<br />
You may consider making a function like this, to detect if the game is in a read-only state:<br />
<br />
// Returns true for spectators, instant replay (during game), archive mode (after game end)<br />
isReadOnly: function () {<br />
return this.isSpectator || typeof g_replayFrom != 'undefined' || g_archive_mode;<br />
}<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of your javascript on the studio (and revert to the original).<br />
<br />
NB: it has been reported that there is an issue with this minifier and percentage values for opacity.<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You can use '''getElementById''' but a longer to type and less handy as it does not do some checks.<br />
Note²: It is safe to use if you don't know if variable is string (id of element) or element itself, i.e. <br />
<pre><br />
foo: function(card) {<br />
card = $(card); // now its node, no need to write if (typeof card === 'string') ...<br />
// but its good idea to check for null here<br />
...<br />
}<br />
</pre><br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
Note²: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
'''Vanila JS style'''<br />
$('my_element').style.display='none'; // set<br />
var display = $('my_element').style.display; // get<br />
$('my_element').style.removeProperty('display'); // remove<br />
<br />
'''dojo.addClass''', '''dojo.removeClass''', '''dojo.hasClass''', '''dojo.toggleClass'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.attr'''<br />
<br />
With dojo.attr you can access or change the value of an attribute or property of any HTML element in your interface.<br />
<br />
Exemple:<br />
<pre><br />
// Get the title of a node<br />
var title = dojo.attr( id, 'title' );<br />
// Change the height of a node<br />
dojo.attr( 'img_growing_tree', 'height', 100 );<br />
</pre><br />
<br />
'''Vanila JS attr'''<br />
<br />
$(token).id=new_id; // set attr for "id"<br />
var id = $(token).id; // get<br />
<br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''Vanila JS query'''<br />
<br />
var cards=document.querySelectorAll(".hand .card");// all cards in all hands<br />
var cards=$('hand').querySelectorAll(".card");// all cards in specific hand<br />
var card=document.querySelector(".hand .card");// first card or null if none (super handy)<br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positive integer: This parameter can be a positive integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place: [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
But you can also relocate elements like that. Note: it won't animate if you do that.<br />
<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
dojo.empty('my_hand');<br />
<br />
'''dojo.destroy'''<br />
<br />
Remove the element<br />
<br />
dojo.query(".green", mynode).forEach(dojo.destroy); // this remove all subnode of class green from mynode<br />
<br />
'''dojo.create'''<br />
<br />
Create element<br />
<br />
dojo.create("div", { class: "yellow_arrow" }, parent); // this creates div with class yellow_array and places it in "parent"<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' ).play();<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments, but it starts the animation. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it. Its starts the animation.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
Note: This wiki was probably written 10 years ago - now all modern broser support 'transform' so just ignore this and use transform.<br />
<br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
…<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
Example: associate a click on an element ("my_element") with one of our methods ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
Same idea but base on query (i.e. all element of 'pet' class)<br />
dojo.query(".pet").connect('onclick', this, 'onPet');<br />
<br />
Note: the methods described here are the only correct ways to associate a player input event to your code, and you should not use anything else.<br />
<br />
'''this.connect'''<br />
function(element, event, handler)<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
this.connect( $('my_element'), 'onclick', 'onClickOnMyElement' );<br />
<br />
Or you can use an in-place handler<br />
<br />
this.connect( $('my_element'), 'onclick', (e) => { console.log('boo'); } );<br />
<br />
Note that this function stores the connection handler. That is the only real difference between '''this.connect''' and '''dojo.connect'''. If you plan to destroy the element you connected, you '''must''' call this.disconnect() to prevent memory leaks.<br />
<br />
<br />
'''this.connectClass'''<br />
function( cssClassName, event, handler )<br />
<br />
Same as connect(), but for all the nodes set with the specified cssClassName.<br />
<pre><br />
this.connectClass('pet', 'onclick', 'onPet');<br />
</pre><br />
'''this.disconnect'''<br />
function( element, event )<br />
<br />
Disconnect event handler (previously registered with this.connect or this.connectClass).<br />
<br />
this.disconnect( $('my_element'), 'onclick');<br />
<br />
Note: dynamic connect/disconnect is for advanced cases ONLY, you should always connect elements statically if possible, i.e. in setup() method.<br />
<br />
'''this.disconnectAll'''<br />
<br />
Disconnect all previously registed event handlers (registered via this.connect or this.connectClass)<br />
<br />
'''this.checkAction'''<br />
function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
return true if action is authorized (i.e: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not authorized (display no message if nomessage parameter is true). <br />
The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt ) {<br />
if( this.checkAction( "my_action" ) ) {<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions'''<br />
function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server. '''It should not be triggered programmatically''', especially not in loops, in callbacks, in notifications, or in onEnteringState/onUpdateActionButtons/onLeavingState, in order not to create race conditions.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. <br />
** Note that "lock: true" must always be specified in this list of parameters in order the interface can be locked during the server call.<br />
** Note: Restricted parameter names (please don't use them):<br />
*** "action"<br />
*** "module"<br />
*** "class"<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine (not used, as all data handling is done via notifications).<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error. if no error this function is called with parameter value false.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
// NB : usually not needed as changes must be handled by notifications<br />
// You should NOT modify the interface in a callback or it will most likely break the framework replays (or make it inaccurate)<br />
// You should NOT make another ajaxcall in a callback in order not to create race conditions<br />
} );<br />
</pre><br />
<br />
Note: to reduce the boilerplate code you can define your own wrapper, which will do checking, locking and allow to skip parameters, for example<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) {<br />
args = [];<br />
}<br />
args.lock = true;<br />
<br />
if (this.checkAction(action)) {<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args,// <br />
this, (result) => { }, handler);<br />
}<br />
},<br />
</pre><br />
This can be called like this which is a lot more compact<br />
<pre><br />
this.ajaxcallwrapper('playDraw');<br />
this.ajaxcallwrapper('playMove', {card: id})<br />
</pre><br />
<br />
<br />
'''this.isInterfaceLocked()'''<br />
<br />
When using "lock: true" in ajax call you can use this function to check if interface is in lock state (it will be locked during server call and notification processing).<br />
This check can be used to block some other interactions which do not result in ajaxcall or if you want to suppress errors.<br />
<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<br />
PHP<br />
<br />
<pre><br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
</pre><br />
<br />
JavaScript<br />
<br />
<pre><br />
//You can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
-----<br />
<br />
It is also possible to control the delay timing dynamically (e.g., using notification args). As an example, maybe your notification 'cardPlayed' should pause for a different amount of time depending on the number or type of cards played.<br />
<br />
For this case, use '''setSynchronous''' without specifying the duration and use '''setSynchronousDuration''' within the notification callback.<br />
<br />
* NOTE: If you forget to invoke '''setSynchronousDuration''', the game will remain paused forever!<br />
<br />
<pre><br />
setupNotifications: function () {<br />
dojo.subscribe( 'cardPlayed', this, 'notif_cardPlayed' );<br />
this.notifqueue.setSynchronous( 'cardPlayed' ); // wait time is dynamic<br />
...<br />
},<br />
<br />
notif_cardPlayed: function (notif) {<br />
// MUST call setSynchronousDuration<br />
<br />
// Example 1: From notification args (PHP)<br />
this.notifqueue.setSynchronousDuration(notif.args.duration);<br />
...<br />
<br />
// Or, example 2: Match the duration to a Dojo animation<br />
var anim = dojo.fx.combine([<br />
...<br />
]);<br />
anim.play();<br />
this.notifqueue.setSynchronousDuration(anim.duration);<br />
},<br />
</pre><br />
<br />
=== Ignoring notifications ===<br />
Sometimes you need to ignore some notification on client side. You don't want them to be shown in game log and you don't want them to be handled.<br />
<br />
The most common use case is when a player gets private information. They will receive a specific notification (such as "You received Ace of Heart"), while other players would receive more generic notification ("Player received a card").<br />
<br />
In X.game.php<br />
<pre><br />
$this->notifyAllPlayers("dealCard", clienttranslate('${player_name} received a card'), [<br />
'player_id' => $playerId,<br />
'player_name' => $this->getActivePlayerName()<br />
]);<br />
<br />
$this->notifyPlayer($playerId, "dealCardPrivate", clienttranslate('You received ${cardName}'), [<br />
"type" => $card["type"],<br />
"cardName" => $this->getCardName($card["type"])<br />
]);<br />
</pre><br />
<br />
The problem with this approach is that the active player will receive two notifications:<br />
* Player1 received a card<br />
* You received Ace of Hearts<br />
<br />
Hence, notification ignoring. Similar to setting a synchronous notification above, you can set up a check whether a notification should be ignored:<br />
<pre><br />
this.notifqueue.setIgnoreNotificationCheck( 'dealCard', (notif) => (notif.args.player_id == this.player_id) );<br />
</pre><br />
<br />
'''setIgnoreNotificationCheck(notificationId, predicate)'''<br />
This method will set a check whether any of notifications of specific type should be ignored.<br />
<br />
The parameters are:<br />
* notificationId: before dispatching any notification of this type, the framework will call predicate to check whether notification should be ignored<br />
* predicate (notif => boolean): a function that will receive notif object and will return true if this specific notification should be ignored<br />
<br />
NOTE: You can think that it could be possible to send generic notification to all other players with notifyAllPlayers and it will seem to work. The problem is that table spectators would miss this notification and their user interface (and game log) wouldn't be updated.<br />
<br />
IMPORTANT: Remember that this notification is ignored on the client side, but was still received by the client. Therefore it shouldn't contain any private information as cheaters can get it. In other words this is not a way to hide information.<br />
<br />
IMPORTANT: When a game is reloaded with F5 or when opening a turn based game, old notifications are replayed as history notification. They are used just to update the game log and are stripped of all arguments except player_id, i18n and any argument present in message. If you use and other argument in your predicate you should preserve it as explained [[Main_game_logic:_yourgamename.game.php#Notify_players|here]].<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', [] );<br />
<br />
'''simplePause''' - This notification will just delay other notifications, maybe useful if you know you need some extra time for animation or something. Requires a time parameter.<br />
<br />
<pre><br />
self::notifyAllPlayers( 'simplePause', '', [ 'time' => 500] ); // time is in milliseconds<br />
</pre><br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening in the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfills one of the end of the game conditions, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of the current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info", "error", or "only_to_log". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background. If set to "only_to_log", the message will be added to the game log but will not popup at the top of the screen.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guideline of BGA is to AVOID the use of confirmation dialogs. Confirmation dialogs slow down the game and bother players. The players know that they have to pay attention to each move when they are playing online.<br />
<br />
The situations where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player does not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?'), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceCloseCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration, offset_x, offset_y );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
'''offset_x''' and '''offset_y''': if both offset_x and offset_y are defined and not null, apply the following offset (in pixels) to the scoring animation. Note that the score is centered in the anchor, so the offsets might have to be negative if you calculate the position.<br />
<br />
<br />
Note: if you want to display successively each score, you can use ''this.notifqueue.setSynchronous()'' function.<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble(anchor_id, text, delay, duration, custom_class)<br />
</pre><br />
<br />
text - what to put in bubble, can be html actually not just text<br />
<br />
delay - in milliseconds is optional (default 0)<br />
<br />
duration - in milliseconds is optional (default 3000)<br />
<br />
custom_class - extra class to add to bubble is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
Note: If you want this visually, but want to take complete control over this bubble and its animation (for example to make it permanent) you can just use div with 'discussion_bubble' class on it, and content of div is what will be shown.<br />
<br />
== Update players score ==<br />
<br />
The column player_score from the player table is automatically loaded into this.scoreCtrl and therefore into the stars location on the player board. This occurs sometime after the <gamename>.js setup() function. However this score must be updated as the game progresses through player notifications (notifs).<br />
<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
Typical usage would be (that will process 'score' notification):<br />
<pre><br />
setupNotifications : function() {<br />
...<br />
dojo.subscribe('score', this, "notif_score");<br />
},<br />
notif_score: function(notif) {<br />
this.scoreCtrl[notif.args.player_id].setValue(notif.args.player_score);<br />
},<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
NOTE: In modern JS there are lambdas that eliminate need for that, the example above will look like this<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), () => {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, (result) => {} );<br />
} ); <br />
</pre><br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
;'''onScreenWidthChange()'''<br />
:This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the :viewport size at the start of the game too.<br />
<br />
<br />
;'''updatePageTitle()'''<br />
:This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before :calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt ) {<br />
...<br />
if ( ... ) {<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
this.addActionButton( ... );<br />
...<br />
return;<br />
}<br />
...<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre><br />
<br />
== BGA Buttons ==<br />
<br />
You can create a custom button, but the BGA framework provides a standard button that requires only .css classes: '''bgabutton''' and '''bgabutton_${color}'''.<br />
<br />
'''Examples:'''<br />
<br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_blue"><span>My blue button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_gray"><span>My gray button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red"><span>My red button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red bgabutton_big"><span>My big red button</span></a><br />
</pre><br />
<br />
<br />
'''Note''': To see it in action, check out ''Coloretto''.<br />
<br />
<br />
'''Disabling:'''<br />
You can disable the '''bgabutton''' by adding the css class '''disabled''' in you js. The disabled button is still visible but is grey and not clickable.<br />
For example in the '''onUpdateActionButtons''' : <br />
<pre>this.addActionButton( 'play_button_id', _('Play 1 to 3 cards'), 'playFunctionButton', null, false, 'blue' ); //Create a blue button<br />
if (Condition == true)<br />
{<br />
dojo.addClass( 'play_button_id', 'disabled');//disable the button<br />
}</pre><br />
<br />
== Sounds ==<br />
<br />
Add a custom sound and make it load with your interface:<br />
<br />
Add this in your template (.tpl) file:<br />
<pre><br />
<audio id="audiosrc_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.mp3" preload="none" autobuffer></audio><br />
<audio id="audiosrc_o_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.ogg" preload="none" autobuffer></audio><br />
</pre><br />
<br />
Note: this is a requirement to provide both a mp3 and a ogg file.<br />
<br />
Play the sound (from your .js file):<br />
<br />
<pre><br />
playSound('<gamename>_<yoursoundname>'); <br />
</pre><br />
<br />
<br />
Disable the standard "move" sound for this move (to replace it with your custom sound):<br />
<br />
Add this to your notification handler:<br />
<br />
<pre><br />
this.disableNextMoveSound();<br />
</pre><br />
<br />
Note: it only disable the sound for the next move.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=8475
Game interface logic: yourgamename.js
2021-06-08T02:51:31Z
<p>JonChambers: /* Notifications */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
<br />
More details:<br />
<br />
;setup(gamedatas) <br />
This method must set up the game user interface according to current game situation specified in parameters.<br />
The method is called each time the game interface is displayed to a player, ie:<br />
<br />
- when the game starts<br />
<br />
- when a player refreshes the game page (F5)<br />
<br />
"gamedatas" argument contains all datas retrieved by your "getAllDatas" PHP method.<br />
<br />
;onEnteringState(stateName, args)<br />
This method is called each time we are entering into a new game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
To access state arguments passed via calling arg* method use args.args.<br />
Typically you would do something only for active player, using this.isCurrentPlayerActive() check.<br />
<br />
'''Warning''': for multipleactiveplayer states:<br />
the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.<br />
If you are doing initialization of some structure which do not depend on active player, you can just replace <br />
if (this.isCurrentPlayerActive()) {<br />
into<br />
if (!this.isSpectator) {<br />
for the main switch in that method.<br />
<br />
;onLeavingState(stateName)<br />
This method is called each time we are leaving a game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar.<br />
To access state arguments passed via calling arg* method use args parameter. Note: args can be null! For game states and when you don't supply state args function it is null.<br />
This method is called when active or multiactive player changes. In classic "activePlayer" state this method is called before the onEnteringState state.<br />
In multipleactiveplayer state it is a mess. The sequencing of call would depends on either you get into that from transition from reloading the whole game (i.e. F5).<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in replay mode <i>during the game</i> (the game is ongoing but the user clicked "reply from this move" in the log)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode <i>after the game</i> (the game has ended)<br />
<br />
; this.instantaneousMode<br />
: Returns true during replay/archive mode if animations should be skipped. Only needed if you are doing custom animations. (The BGA-provided animation functions like <i>this.slideToObject()</i> automatically handle instantaneous mode.)<br />
: Technically, when you click "replay from move #20", the system replays the game from the very beginning with moves 0 - 19 happening in instantaneous mode and moves 20+ happening in normal mode.<br />
<br />
---<br />
<br />
You may consider making a function like this, to detect if the game is in a read-only state:<br />
<br />
// Returns true for spectators, instant replay (during game), archive mode (after game end)<br />
isReadOnly: function () {<br />
return this.isSpectator || typeof g_replayFrom != 'undefined' || g_archive_mode;<br />
}<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of your javascript on the studio (and revert to the original).<br />
<br />
NB: it has been reported that there is an issue with this minifier and percentage values for opacity.<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You can use '''getElementById''' but a longer to type and less handy as it does not do some checks.<br />
Note²: It is safe to use if you don't know if variable is string (id of element) or element itself, i.e. <br />
<pre><br />
foo: function(card) {<br />
card = $(card); // now its node, no need to write if (typeof card === 'string') ...<br />
// but its good idea to check for null here<br />
...<br />
}<br />
</pre><br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
Note²: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
'''Vanila JS style'''<br />
$('my_element').style.display='none'; // set<br />
var display = $('my_element').style.display; // get<br />
$('my_element').style.removeProperty('display'); // remove<br />
<br />
'''dojo.addClass''', '''dojo.removeClass''', '''dojo.hasClass''', '''dojo.toggleClass'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.attr'''<br />
<br />
With dojo.attr you can access or change the value of an attribute or property of any HTML element in your interface.<br />
<br />
Exemple:<br />
<pre><br />
// Get the title of a node<br />
var title = dojo.attr( id, 'title' );<br />
// Change the height of a node<br />
dojo.attr( 'img_growing_tree', 'height', 100 );<br />
</pre><br />
<br />
'''Vanila JS attr'''<br />
<br />
$(token).id=new_id; // set attr for "id"<br />
var id = $(token).id; // get<br />
<br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''Vanila JS query'''<br />
<br />
var cards=document.querySelectorAll(".hand .card");// all cards in all hands<br />
var cards=$('hand').querySelectorAll(".card");// all cards in specific hand<br />
var card=document.querySelector(".hand .card");// first card or null if none (super handy)<br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positive integer: This parameter can be a positive integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place: [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
But you can also relocate elements like that. Note: it won't animate if you do that.<br />
<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
dojo.empty('my_hand');<br />
<br />
'''dojo.destroy'''<br />
<br />
Remove the element<br />
<br />
dojo.query(".green", mynode).forEach(dojo.destroy); // this remove all subnode of class green from mynode<br />
<br />
'''dojo.create'''<br />
<br />
Create element<br />
<br />
dojo.create("div", { class: "yellow_arrow" }, parent); // this creates div with class yellow_array and places it in "parent"<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' ).play();<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments, but it starts the animation. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it. Its starts the animation.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
Note: This wiki was probably written 10 years ago - now all modern broser support 'transform' so just ignore this and use transform.<br />
<br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
…<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
Example: associate a click on an element ("my_element") with one of our methods ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
Same idea but base on query (i.e. all element of 'pet' class)<br />
dojo.query(".pet").connect('onclick', this, 'onPet');<br />
<br />
Note: the methods described here are the only correct ways to associate a player input event to your code, and you should not use anything else.<br />
<br />
'''this.connect'''<br />
function(element, event, handler)<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
this.connect( $('my_element'), 'onclick', 'onClickOnMyElement' );<br />
<br />
Or you can use an in-place handler<br />
<br />
this.connect( $('my_element'), 'onclick', (e) => { console.log('boo'); } );<br />
<br />
Note that this function stores the connection handler. That is the only real difference between '''this.connect''' and '''dojo.connect'''. If you plan to destroy the element you connected, you '''must''' call this.disconnect() to prevent memory leaks.<br />
<br />
<br />
'''this.connectClass'''<br />
function( cssClassName, event, handler )<br />
<br />
Same as connect(), but for all the nodes set with the specified cssClassName.<br />
<pre><br />
this.connectClass('pet', 'onclick', 'onPet');<br />
</pre><br />
'''this.disconnect'''<br />
function( element, event )<br />
<br />
Disconnect event handler (previously registered with this.connect or this.connectClass).<br />
<br />
this.disconnect( $('my_element'), 'onclick');<br />
<br />
Note: dynamic connect/disconnect is for advanced cases ONLY, you should always connect elements statically if possible, i.e. in setup() method.<br />
<br />
'''this.disconnectAll'''<br />
<br />
Disconnect all previously registed event handlers (registered via this.connect or this.connectClass)<br />
<br />
'''this.checkAction'''<br />
function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
return true if action is authorized (i.e: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not authorized (display no message if nomessage parameter is true). <br />
The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt ) {<br />
if( this.checkAction( "my_action" ) ) {<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions'''<br />
function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server. '''It should not be triggered programmatically''', especially not in loops, in callbacks, in notifications, or in onEnteringState/onUpdateActionButtons/onLeavingState, in order not to create race conditions.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. <br />
** Note that "lock: true" must always be specified in this list of parameters in order the interface can be locked during the server call.<br />
** Note: Restricted parameter names (please don't use them):<br />
*** "action"<br />
*** "module"<br />
*** "class"<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine (not used, as all data handling is done via notifications).<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error. if no error this function is called with parameter value false.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
// NB : usually not needed as changes must be handled by notifications<br />
// You should NOT modify the interface in a callback or it will most likely break the framework replays (or make it inaccurate)<br />
// You should NOT make another ajaxcall in a callback in order not to create race conditions<br />
} );<br />
</pre><br />
<br />
Note: to reduce the boilerplate code you can define your own wrapper, which will do checking, locking and allow to skip parameters, for example<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) {<br />
args = [];<br />
}<br />
args.lock = true;<br />
<br />
if (this.checkAction(action)) {<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args,// <br />
this, (result) => { }, handler);<br />
}<br />
},<br />
</pre><br />
This can be called like this which is a lot more compact<br />
<pre><br />
this.ajaxcallwrapper('playDraw');<br />
this.ajaxcallwrapper('playMove', {card: id})<br />
</pre><br />
<br />
<br />
'''this.isInterfaceLocked()'''<br />
<br />
When using "lock: true" in ajax call you can use this function to check if interface is in lock state (it will be locked during server call and notification processing).<br />
This check can be used to block some other interactions which do not result in ajaxcall or if you want to suppress errors.<br />
<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<pre><br />
// If you did this on PHP side:<br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
<br />
// On Javascript side, you can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
-----<br />
<br />
It is also possible to control the delay timing dynamically (e.g., using notification args). As an example, maybe your notification 'cardPlayed' should pause for a different amount of time depending on the number or type of cards played.<br />
<br />
For this case, use '''setSynchronous''' without specifying the duration and use '''setSynchronousDuration''' within the notification callback.<br />
<br />
* NOTE: If you forget to invoke '''setSynchronousDuration''', the game will remain paused forever!<br />
<br />
<pre><br />
setupNotifications: function () {<br />
dojo.subscribe( 'cardPlayed', this, 'notif_cardPlayed' );<br />
this.notifqueue.setSynchronous( 'cardPlayed' ); // wait time is dynamic<br />
...<br />
},<br />
<br />
notif_cardPlayed: function (notif) {<br />
// MUST call setSynchronousDuration<br />
<br />
// Example 1: From notification args (PHP)<br />
this.notifqueue.setSynchronousDuration(notif.args.duration);<br />
...<br />
<br />
// Or, example 2: Match the duration to a Dojo animation<br />
var anim = dojo.fx.combine([<br />
...<br />
]);<br />
anim.play();<br />
this.notifqueue.setSynchronousDuration(anim.duration);<br />
},<br />
</pre><br />
<br />
=== Ignoring notifications ===<br />
Sometimes you need to ignore some notification on client side. You don't want them to be shown in game log and you don't want them to be handled.<br />
<br />
The most common use case is when a player gets private information. They will receive a specific notification (such as "You received Ace of Heart"), while other players would receive more generic notification ("Player received a card").<br />
<br />
In X.game.php<br />
<pre><br />
$this->notifyAllPlayers("dealCard", clienttranslate('${player_name} received a card'), [<br />
'player_id' => $playerId,<br />
'player_name' => $this->getActivePlayerName()<br />
]);<br />
<br />
$this->notifyPlayer($playerId, "dealCardPrivate", clienttranslate('You received ${cardName}'), [<br />
"type" => $card["type"],<br />
"cardName" => $this->getCardName($card["type"])<br />
]);<br />
</pre><br />
<br />
The problem with this approach is that the active player will receive two notifications:<br />
* Player1 received a card<br />
* You received Ace of Hearts<br />
<br />
Hence, notification ignoring. Similar to setting a synchronous notification above, you can set up a check whether a notification should be ignored:<br />
<pre><br />
this.notifqueue.setIgnoreNotificationCheck( 'dealCard', (notif) => (notif.args.player_id == this.player_id) );<br />
</pre><br />
<br />
'''setIgnoreNotificationCheck(notificationId, predicate)'''<br />
This method will set a check whether any of notifications of specific type should be ignored.<br />
<br />
The parameters are:<br />
* notificationId: before dispatching any notification of this type, the framework will call predicate to check whether notification should be ignored<br />
* predicate (notif => boolean): a function that will receive notif object and will return true if this specific notification should be ignored<br />
<br />
NOTE: You can think that it could be possible to send generic notification to all other players with notifyAllPlayers and it will seem to work. The problem is that table spectators would miss this notification and their user interface (and game log) wouldn't be updated.<br />
<br />
IMPORTANT: Remember that this notification is ignored on the client side, but was still received by the client. Therefore it shouldn't contain any private information as cheaters can get it. In other words this is not a way to hide information.<br />
<br />
IMPORTANT: When a game is reloaded with F5 or when opening a turn based game, old notifications are replayed as history notification. They are used just to update the game log and are stripped of all arguments except player_id, i18n and any argument present in message. If you use and other argument in your predicate you should preserve it as explained [[Main_game_logic:_yourgamename.game.php#Notify_players|here]].<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', [] );<br />
<br />
'''simplePause''' - This notification will just delay other notifications, maybe useful if you know you need some extra time for animation or something. Requires a time parameter.<br />
<br />
<pre><br />
self::notifyAllPlayers( 'simplePause', '', [ 'time' => 500] ); // time is in milliseconds<br />
</pre><br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening in the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfills one of the end of the game conditions, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of the current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info", "error", or "only_to_log". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background. If set to "only_to_log", the message will be added to the game log but will not popup at the top of the screen.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guideline of BGA is to AVOID the use of confirmation dialogs. Confirmation dialogs slow down the game and bother players. The players know that they have to pay attention to each move when they are playing online.<br />
<br />
The situations where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player does not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?'), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceCloseCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration, offset_x, offset_y );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
'''offset_x''' and '''offset_y''': if both offset_x and offset_y are defined and not null, apply the following offset (in pixels) to the scoring animation. Note that the score is centered in the anchor, so the offsets might have to be negative if you calculate the position.<br />
<br />
<br />
Note: if you want to display successively each score, you can use ''this.notifqueue.setSynchronous()'' function.<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble(anchor_id, text, delay, duration, custom_class)<br />
</pre><br />
<br />
text - what to put in bubble, can be html actually not just text<br />
<br />
delay - in milliseconds is optional (default 0)<br />
<br />
duration - in milliseconds is optional (default 3000)<br />
<br />
custom_class - extra class to add to bubble is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
Note: If you want this visually, but want to take complete control over this bubble and its animation (for example to make it permanent) you can just use div with 'discussion_bubble' class on it, and content of div is what will be shown.<br />
<br />
== Update players score ==<br />
<br />
The column player_score from the player table is automatically loaded into this.scoreCtrl and therefore into the stars location on the player board. This occurs sometime after the <gamename>.js setup() function. However this score must be updated as the game progresses through player notifications (notifs).<br />
<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
Typical usage would be (that will process 'score' notification):<br />
<pre><br />
setupNotifications : function() {<br />
...<br />
dojo.subscribe('score', this, "notif_score");<br />
},<br />
notif_score: function(notif) {<br />
this.scoreCtrl[notif.args.player_id].setValue(notif.args.player_score);<br />
},<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
NOTE: In modern JS there are lambdas that eliminate need for that, the example above will look like this<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), () => {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, (result) => {} );<br />
} ); <br />
</pre><br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
;'''onScreenWidthChange()'''<br />
:This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the :viewport size at the start of the game too.<br />
<br />
<br />
;'''updatePageTitle()'''<br />
:This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before :calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt ) {<br />
...<br />
if ( ... ) {<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
this.addActionButton( ... );<br />
...<br />
return;<br />
}<br />
...<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre><br />
<br />
== BGA Buttons ==<br />
<br />
You can create a custom button, but the BGA framework provides a standard button that requires only .css classes: '''bgabutton''' and '''bgabutton_${color}'''.<br />
<br />
'''Examples:'''<br />
<br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_blue"><span>My blue button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_gray"><span>My gray button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red"><span>My red button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red bgabutton_big"><span>My big red button</span></a><br />
</pre><br />
<br />
<br />
'''Note''': To see it in action, check out ''Coloretto''.<br />
<br />
<br />
'''Disabling:'''<br />
You can disable the '''bgabutton''' by adding the css class '''disabled''' in you js. The disabled button is still visible but is grey and not clickable.<br />
For example in the '''onUpdateActionButtons''' : <br />
<pre>this.addActionButton( 'play_button_id', _('Play 1 to 3 cards'), 'playFunctionButton', null, false, 'blue' ); //Create a blue button<br />
if (Condition == true)<br />
{<br />
dojo.addClass( 'play_button_id', 'disabled');//disable the button<br />
}</pre><br />
<br />
== Sounds ==<br />
<br />
Add a custom sound and make it load with your interface:<br />
<br />
Add this in your template (.tpl) file:<br />
<pre><br />
<audio id="audiosrc_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.mp3" preload="none" autobuffer></audio><br />
<audio id="audiosrc_o_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.ogg" preload="none" autobuffer></audio><br />
</pre><br />
<br />
Note: this is a requirement to provide both a mp3 and a ogg file.<br />
<br />
Play the sound (from your .js file):<br />
<br />
<pre><br />
playSound('<gamename>_<yoursoundname>'); <br />
</pre><br />
<br />
<br />
Disable the standard "move" sound for this move (to replace it with your custom sound):<br />
<br />
Add this to your notification handler:<br />
<br />
<pre><br />
this.disableNextMoveSound();<br />
</pre><br />
<br />
Note: it only disable the sound for the next move.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=8474
Main game logic: yourgamename.game.php
2021-06-08T02:37:25Z
<p>JonChambers: /* Notify players */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* Constructor: where you define global variables.<br />
* setupNewGame: initial setup of the game. Takes an array of players, indexed by player_id. Structure of each player includes player_name, player_canal, player_avatar, and flags indicating admin/ai/premium/order/language/beginner.<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game. Returned value must include ['players'][playerId]['score'] for scores to populate when F5 is pressed.<br />
* getGameProgression: where you compute the game progression indicator. Returns a number indicating percent of progression (0-100)<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions ([https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php more info here]). <br />
* Game state arguments: methods to return additional data on specific game states ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#args more info here]).<br />
* Game state actions: the logic to run when entering a new game state ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#action more info here]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
* upgradeTableDb: function to migrate database if you change it after release on production.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
; isSpectator()<br />
: Check the "current_player" spectator status. If true, the user accessing the game is a spectator (not part of the game). For this user, the interface should display all public information, and no private information (like a friend sitting at the same table as players and just spectating the game).<br />
<br />
; getActivePlayerColor()<br />
: This function does not seems to exist in API, if you need it here is implementation<br />
function getActivePlayerColor() {<br />
$player_id = self::getActivePlayerId();<br />
$players = self::loadPlayersBasicInfos();<br />
if( isset( $players[ $player_id ]) )<br />
return $players[ $player_id ]['player_color'];<br />
else<br />
return null;<br />
}<br />
<br />
== Accessing the database ==<br />
<br />
The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.<br />
<br />
'''IMPORTANT'''<br />
<br />
BGA uses [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. This means that your database changes WON'T BE APPLIED to the database until your request ends normally (web request, not database request). Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed. This also means that you need not (and in fact cannot) use your own transactions for multiple related database operations.<br />
<br />
However there sets of database operation that initite implicit commit (most common mistake is to use "TRUNCATE"), you cannot use these operations during the game, it breaks the unrolling of transactions and will lead to nasty issues<br />
(https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit).<br />
<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE/INSERT query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE/INSERT queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array is an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Same as getCollectionFromDB($sdl), but raise an exception if the collection is empty. Note: this function does NOT have 2nd argument as previous one does.<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Similar to previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: The result is the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of __construct() method of ''yourgamename.game.php''. This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 80 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multipleactiveplayer"<br />
: Note: avoid using this method in a "multipleactiveplayer" state because it does not mean anything.<br />
<br />
=== Multiple activate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (this will trigger '''onUpdateActionButtons''').<br />
: Usually, you use this method at the beginning of a game state (e.g., "stGameState") which transitions to a ''multipleactiveplayer'' state in which multiple players have to perform some action. Do not use this method if you going to make some more changes in the active player list. (I.e., if you want to take away multipleactiveplayer status immediately afterwards, use setPlayersMultiactive instead.)<br />
<br />
Example of usage:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
<br />
And this is the state declaration:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'multiPlayerDoSomething',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "actionBla" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players whose state changed.<br />
: "players" is the array of player id that should be made active.<br />
: If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action. It is also possible to call it directly from multiplayer action handler.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
Example of usage (see state declaration of playerTurnPlace above):<br />
<pre><br />
function actionBla($args) {<br />
self::checkAction('actionBla');<br />
// handle the action using $this->getCurrentPlayerId()<br />
$this->gamestate->setPlayerNonMultiactive( $this->getCurrentPlayerId(), 'next');<br />
}<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; updating database manually<br />
: Use this helper function to change multiactive state without sending notification<br />
<pre><br />
/**<br />
* Changes values of multiactivity in db, does not sent notifications.<br />
* To send notifications after use updateMultiactiveOrNextState<br />
* @param number $player_id, player id <=0 or null - means ALL<br />
* @param number $value - 1 multiactive, 0 non multiactive<br />
*/<br />
function dbSetPlayerMultiactive($player_id = -1, $value = 1) {<br />
if (! $value)<br />
$value = 0;<br />
else<br />
$value = 1;<br />
$sql = "UPDATE player SET player_is_multiactive = '$value' WHERE player_zombie = 0 and player_eliminated = 0";<br />
if ($player_id > 0) {<br />
$sql .= " AND player_id = $player_id";<br />
}<br />
self::DbQuery($sql);<br />
}<br />
</pre><br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter is the name of the transition, and NOT the name of the target game state, see [[Your game state machine: states.inc.php]] for more information about states.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if the current player can perform a specific action in the current game state, and optionally throw an exception if they can't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game. It should not be called from methods where the current player is not necessarily the active player, otherwise it may fail with an "It is not your turn" exception.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
This is how PHP action looks that returns player to active state (only for multiplayeractive states). To be able to execute on js side do not checkAction on js side for this specific one.<br />
<br />
function actionUnpass() {<br />
$this->gamestate->checkPossibleAction('actionUnpass'); // player chane mind about passing while others were thinking<br />
$this->gamestate->setPlayersMultiactive(array ($this->getCurrentPlayerId() ), 'error', false);<br />
}<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table. However there no 0 index here.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
'''createNextPlayerTable( $players, $bLoop=true )'''<br />
<br />
Using $players array creates a map of current => next as in example from getNextPlayerTable(), however you can use custom order here. <br />
Parmeter $bLoop is true if last player points to first, false otherwise.<br />
In any case index 0 points to first player (first element of $players array). $players is array of player ids in desired order.<br />
<br />
N.B: This function '''DOES NOT''' allow to change players turn order!<br />
<br />
Example of usage:<br />
<pre><br />
function getNextPlayerTableCustom() {<br />
$starting = $this->getStartingPlayer(); // custom function to get starting player<br />
$player_ids = $this->getPlayerIdsInOrder($starting); // custom function to create players array starting from starting player<br />
return self::createNextPlayerTable($player_ids, false); // create next player table in custom order<br />
}<br />
</pre><br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translated.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.<br />
<br />
'''Important''': NO private data must be sent with this method, as a cheater could see it even if it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
'''Important''': When the game page is reloaded (i.e. F5 or when loading turn based game) all previous notifications are replayed as history notifications. These notifications do not trigger notification handlers and are used basically to build the game log. Because of that most of the notification arguments, except i18n, player_id and all arguments used in the message, are removed from these history notifications. If you need additional arguments in history notifications you can add special field <b>preserve</b> to notification arguments, like this:<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y,<br />
'preserve' => [ 'x', 'y' ]<br />
) );<br />
</pre><br />
In this example, fields x and y will be preserved when replaying history notification at the game load.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games replay and tutorials may not work, since they use stored notifications<br />
* When you read log preview for old games its unreadable (this is log before you enter the game replay, useful for troubleshooting or game analysis)<br />
* Its more data to transfer and store in db<br />
* Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
Special handling of arguments:<br />
* ${player_name} - this will be wrapped in html and text shown using color of the corresponding player, some colors also have reserved background. This will apply recursively as well.<br />
* ${player_name2} - same <br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log. If you want a second player name in the log, name the variable ${player_name2}, etc.<br />
<br />
Note that spectators cannot be notified using this method, because their player ID is not available via loadPlayersBasicInfos() or otherwise. You must use notifyAllPlayers() for any notification that spectators should get.<br />
<br />
== About random and randomness ==<br />
<br />
A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dice and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
This is a BGA framework function that provides you a random number between "min" and "max" (inclusive), using the best available random method available on the system.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
As of now, bga_rand is based on the PHP function "random_int", which ensures a cryptographic level of randomness.<br />
<br />
In particular, it is '''mandatory''' to use it for all '''dice throw''' (ie: games using other methods for dice throwing will be rejected by BGA during review).<br />
<br />
Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
To shuffle items, like a pile of cards, the best way is to use the BGA PHP [[Deck]] component and to use "shuffle" method. This ensures that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.<br />
<br />
As of now, the Deck component shuffle method is based on PHP "shuffle" method, which has quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as they are based on PHP "shuffle" function (or similar, like "array_rand").<br />
<br />
=== Other methods ===<br />
<br />
Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistic is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you define statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
<br />
This method must be called for each statistic of your game, in your setupNewGame method.<br />
If you neglect to call this for a statistic, and also do not update the value during the course of a certain game using setStat or incStat, the value of the stat will be undefined rather than 0. This will result in it being ignored at the end of the game, as if it didn't apply to that particular game, and excluded from cumulative statistics. As a consequence - if do not want statistic to be applied, do not init it, or call set or inc on it.<br />
<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistic, or "player" if this is a player statistic.<br />
<br />
'$name' is the name of your statistic, as it has been defined in your stats.inc.php file.<br />
<br />
'$value' is the initial value of the statistic. If this is a player statistic and if the player is not specified by "$player_id" argument, the value is set for ALL players.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone win/lose together in a full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
Assign a score of zero to everyone if it's a loss.<br />
Assign the same score > 0 to everyone if it's a win.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone loses = everyone is tied. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.inc.php:<br />
<br />
<pre><br />
// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative or zero score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.<br />
<br />
This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* the player is informed in a dialog box that he no longer have to play and can start another game if he/she wants too (with buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.<br />
* When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
These functions should have been API but they are not, just add them to your php game and use for every game.<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see [[BGA Undo policy]]). Cannot use in multiactivate state or in game state where next state is multiactive.<br />
: Note: this function does not actually do anything when it is called, it only raises the flag to store the database AFTER transaction is over. So the actual state will be saved when you exit the function calling it (technically before first queued notification is sent, which matter if you transition to game state not to user state after), which may affect what you end up saved.<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
function actionUndo() {<br />
self::checkAction('actionUndo');<br />
$this->undoRestorePoint();<br />
$this->gamestate->nextState('next'); // transition to single player state (i.e. beginning of player actions for this turn)<br />
}<br />
<br />
== Managing errors and exceptions ==<br />
<br />
Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that they are not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: Base class to notify a system exception. The message will be hidden from the user, but show in the logs. Use this if the message contains technical information.<br />
: You shouldn't use this type of exception except if you think the information shown could be critical. Indeed: a generic error message will be shown to the user, so it's going to be difficult for you to see what happened.<br />
<br />
== Zombie mode ==<br />
<br />
When a player leaves a game for any reason (expelled, quit), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but this is cool if the other players can finish the game anyway. That's why zombie mode exists: allow the other player to finish the game, even if the situation is not ideal.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
* Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it. Trying to end the game is not supported by the framework and will likely cause unexpected errors.<br />
<br />
Most of the time, the best thing to do when it is zombie player turn is to jump immediately to a state where he is not active anymore. For example, if he is in a game state where he has a choice between playing A and playing B, the best thing to do is NOT to choose A or B, but to pass. So, even if there's no "pass" action in the rules, add a "zombiepass" transitition in your game state and use it.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
// NB: this parameter is used only to flag games supporting this feature; you must use (or not use) reattributeColorsBasedOnPreferences PHP method to actually enable or disable the feature.<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.<br />
* your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Legacy games API ==<br />
<br />
For some very specific games ("legacy", "campaign"), you need to keep some informations from a game to another.<br />
<br />
This should be an exceptional situation: the legacy API is costing resources on Board Game Arena databases, and is slowing down the game setup process + game end of game process. Please do not use it for things like:<br />
* keeping a player preference/settings (=> player preferences and game options should be used instead)<br />
* keeping a statistics, a score, or a ranking, while it is not planned in the physical board game, or while there is no added value compared to BGA statistics / rankings.<br />
<br />
You should use it for:<br />
* legacy games: when some components of the game has been altered in a previous game and should be kept as it is.<br />
* "campaign style" games: when a player is getting a "reward" at the end of a game, and should be able to use it in further games.<br />
<br />
Important: you cannot store more than 64k of data (serialized as JSON) per player per game. If you go over 64k, storeLegacyData function is going to FAIL, and there is a risk to create a major bug (= players blocked) in your game. You MUST make sure that no more than 64k of data is used for each player for your game. For example, if you are implementing a "campaign style" game and if you allow a player to start multiple campaign, you must LIMIT the number of different campaign so that the total data size to not go over the limit. We strongly recommend you to use this:<br />
<br />
try <br />
{<br />
$this->storeLegacyTeamData( 'my_variable', $my_data );<br />
}<br />
catch( feException $e )<br />
{<br />
if( $e->getCode() == FEX_legacy_size_exceeded )<br />
{<br />
// Do something here to free some space in Legacy data (ex: by removing some variables)<br />
}<br />
else<br />
throw $e;<br />
}<br />
<br />
The keys may only contain letters and numbers, underscore seems not to be allowed.<br />
<br />
; function storeLegacyData( $player_id, $key, $data, $ttl = 365 )<br />
: Store some data associated with $key for the given user / current game<br />
: In the opposite of all other game data, this data will PERSIST after the end of this table, and can be re-used<br />
: in a future table with the same game.<br />
: IMPORTANT: The only possible place where you can use this method is when the game is over at your table (last game action). Otherwise, there is a risk of conflicts between ongoing games. <br />
: TTL is a time-to-live: the maximum, and default, is 365 days.<br />
: In any way, the total data (= all keys) you can store for a given user+game is 64k (note: data is store serialized as JSON data)<br />
<br />
; function retrieveLegacyData( $player_id, $key )<br />
: Get data associated with $key for the current game<br />
: This data is common to ALL tables from the same game for this player, and persist from one table to another.<br />
: Note: calling this function has an important cost => please call it few times (possibly: only ONCE) for each player for 1 game if possible<br />
: Note: you can use '%' in $key to retrieve all keys matching the given patterns<br />
<br />
; function removeLegacyData( $player_id, $key )<br />
: Remove some legacy data with the given key<br />
: (useful to free some data to avoid going over 64k)<br />
<br />
<br />
<br />
; function storeLegacyTeamData( $key, $data, $ttl = 365 )<br />
: Same as storeLegacyData, except that it stores some data for the whole team within the current table<br />
: Ie: if players A, B and C are at a table, the legacy data will be saved for future table with (exactly) A, B and C on the table.<br />
: This is useful for games which are intended to be played several time by the same team.<br />
: Note: the data total size is still limited, so you must implement catch the FEX_legacy_size_exceeded exception if it happens<br />
<br />
; function retrieveLegacyTeamData($key)<br />
: Same as retrieveLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
; function removeLegacyTeamData()<br />
: Same as removeLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
== Language dependent games API ==<br />
<br />
This API is used for games that are heavily language dependent. Two most common use cases are:<br />
* Games that have a language dependent component that are not necessarily translatable, typically a list of words. (Think of games like Codenames, Decrypto, Just One...)<br />
* Games with massive communication where players would like to ensure that all participants speak the same language. (Think of games like Werewolf, The Resistance, maybe even dixit...)<br />
<br />
If this option is used, the table created will be limited only to users that have specific language in their profile. Player starting the game would be able to chose one of the languages he speaks.<br />
<br />
There is a new property language_dependency in gameinfos.inc.php which can be set like this:<br />
'language_dependecy' => false, //or if the property is missing, the game is not language dependent<br />
'language_dependecy' => true, //all players at the table must speak the same language<br />
'language_dependecy' => array( 1 => 'en', 2 => 'fr', 3 => 'it' ), //1-based list of supported languages<br />
<br />
In the gamename.game.php file, you can get the id of selected language with the method '''getGameLanguage'''.<br />
; function getGameLanguage()<br />
: Returns an index of the selected language as defined in gameinfos.inc.php.<br />
<br />
Languages currently available on BGA are:<br />
'ar' => array( 'name' => "العربية", 'code' => 'ar_AE' ), // Arabic<br />
'be' => array( 'name' => "беларуская мова", 'code' => 'be_BY' ), // Belarusian<br />
'bn' => array( 'name' => "বাংলা", 'code' => 'bn_BD' ), // Bengali<br />
'bg' => array( 'name' => "български език", 'code' => 'bg_BG' ), // Bulgarian<br />
'ca' => array( 'name' => "català", 'code' => 'ca_ES' ), // Catalan<br />
'cs' => array( 'name' => "čeština", 'code' => 'cs_CZ' ), // Czech<br />
'da' => array( 'name' => "dansk", 'code' => 'da_DK' ), // Danish<br />
'de' => array( 'name' => "deutsch", 'code' => 'de_DE' ), // German<br />
'el' => array( 'name' => "Ελληνικά", 'code' => 'el_GR' ), // Greek<br />
'en' => array( 'name' => "English", 'code' => 'en_US' ), // English<br />
'es' => array( 'name' => "español", 'code' => 'es_ES' ), // Spanish<br />
'et' => array( 'name' => "eesti keel", 'code' => 'et_EE' ), // Estonian <br />
'fi' => array( 'name' => "suomi", 'code' => 'fi_FI' ), // Finnish<br />
'fr' => array( 'name' => "français", 'code' => 'fr_FR' ), // French<br />
'he' => array( 'name' => "עברית", 'code' => 'he_IL' ), // Hebrew <br />
'hi' => array( 'name' => "हिन्दी", 'code' => 'hi_IN' ), // Hindi<br />
'hr' => array( 'name' => "Hrvatski", 'code' => 'hr_HR' ), // Croatian<br />
'hu' => array( 'name' => "magyar", 'code' => 'hu_HU' ), // Hungarian<br />
'id' => array( 'name' => "Bahasa Indonesia", 'code' => 'id_ID' ), // Indonesian<br />
'ms' => array( 'name' => "Bahasa Malaysia", 'code' => 'ms_MY' ), // Malaysian<br />
'it' => array( 'name' => "italiano", 'code' => 'it_IT' ), // Italian<br />
'ja' => array( 'name' => "日本語", 'code' => 'ja_JP' ), // Japanese<br />
'jv' => array( 'name' => "Basa Jawa", 'code' => 'jv_JV' ), // Javanese <br />
'ko' => array( 'name' => "한국어", 'code' => 'ko_KR' ), // Korean<br />
'lt' => array( 'name' => "lietuvių", 'code' => 'lt_LT' ), // Lithuanian<br />
'lv' => array( 'name' => "latviešu", 'code' => 'lv_LV' ), // Latvian<br />
'nl' => array( 'name' => "nederlands", 'code' => 'nl_NL' ), // Dutch<br />
'no' => array( 'name' => "norsk", 'code' => 'nb_NO' ), // Norwegian<br />
'oc' => array( 'name' => "occitan", 'code' => 'oc_FR' ), // Occitan<br />
'pl' => array( 'name' => "polski", 'code' => 'pl_PL' ), // Polish<br />
'pt' => array( 'name' => "português", 'code' => 'pt_PT' ), // Portuguese<br />
'ro' => array( 'name' => "română", 'code' => 'ro_RO' ), // Romanian<br />
'ru' => array( 'name' => "Русский язык", 'code' => 'ru_RU' ), // Russian<br />
'sk' => array( 'name' => "slovenčina", 'code' => 'sk_SK' ), // Slovak<br />
'sl' => array( 'name' => "slovenščina", 'code' => 'sl_SI' ), // Slovenian <br />
'sr' => array( 'name' => "Српски", 'code' => 'sr_RS' ), // Serbian <br />
'sv' => array( 'name' => "svenska", 'code' => 'sv_SE' ), // Swedish<br />
'tr' => array( 'name' => "Türkçe", 'code' => 'tr_TR' ), // Turkish <br />
'uk' => array( 'name' => "Українська мова", 'code' => 'uk_UA' ), // Ukrainian<br />
'zh' => array( 'name' => "中文 (漢)", 'code' => 'zh_TW' ), // Traditional Chinese (Hong Kong, Macau, Taiwan)<br />
'zh-cn' => array( 'name' => "中文 (汉)", 'code' => 'zh_CN' ), // Simplified Chinese (Mainland China, Singapore)<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=8473
Main game logic: yourgamename.game.php
2021-06-08T02:33:04Z
<p>JonChambers: /* Notify players */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* Constructor: where you define global variables.<br />
* setupNewGame: initial setup of the game. Takes an array of players, indexed by player_id. Structure of each player includes player_name, player_canal, player_avatar, and flags indicating admin/ai/premium/order/language/beginner.<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game. Returned value must include ['players'][playerId]['score'] for scores to populate when F5 is pressed.<br />
* getGameProgression: where you compute the game progression indicator. Returns a number indicating percent of progression (0-100)<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions ([https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php more info here]). <br />
* Game state arguments: methods to return additional data on specific game states ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#args more info here]).<br />
* Game state actions: the logic to run when entering a new game state ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#action more info here]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
* upgradeTableDb: function to migrate database if you change it after release on production.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
; isSpectator()<br />
: Check the "current_player" spectator status. If true, the user accessing the game is a spectator (not part of the game). For this user, the interface should display all public information, and no private information (like a friend sitting at the same table as players and just spectating the game).<br />
<br />
; getActivePlayerColor()<br />
: This function does not seems to exist in API, if you need it here is implementation<br />
function getActivePlayerColor() {<br />
$player_id = self::getActivePlayerId();<br />
$players = self::loadPlayersBasicInfos();<br />
if( isset( $players[ $player_id ]) )<br />
return $players[ $player_id ]['player_color'];<br />
else<br />
return null;<br />
}<br />
<br />
== Accessing the database ==<br />
<br />
The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.<br />
<br />
'''IMPORTANT'''<br />
<br />
BGA uses [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. This means that your database changes WON'T BE APPLIED to the database until your request ends normally (web request, not database request). Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed. This also means that you need not (and in fact cannot) use your own transactions for multiple related database operations.<br />
<br />
However there sets of database operation that initite implicit commit (most common mistake is to use "TRUNCATE"), you cannot use these operations during the game, it breaks the unrolling of transactions and will lead to nasty issues<br />
(https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit).<br />
<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE/INSERT query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE/INSERT queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array is an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Same as getCollectionFromDB($sdl), but raise an exception if the collection is empty. Note: this function does NOT have 2nd argument as previous one does.<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Similar to previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: The result is the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of __construct() method of ''yourgamename.game.php''. This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 80 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multipleactiveplayer"<br />
: Note: avoid using this method in a "multipleactiveplayer" state because it does not mean anything.<br />
<br />
=== Multiple activate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (this will trigger '''onUpdateActionButtons''').<br />
: Usually, you use this method at the beginning of a game state (e.g., "stGameState") which transitions to a ''multipleactiveplayer'' state in which multiple players have to perform some action. Do not use this method if you going to make some more changes in the active player list. (I.e., if you want to take away multipleactiveplayer status immediately afterwards, use setPlayersMultiactive instead.)<br />
<br />
Example of usage:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
<br />
And this is the state declaration:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'multiPlayerDoSomething',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "actionBla" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players whose state changed.<br />
: "players" is the array of player id that should be made active.<br />
: If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action. It is also possible to call it directly from multiplayer action handler.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
Example of usage (see state declaration of playerTurnPlace above):<br />
<pre><br />
function actionBla($args) {<br />
self::checkAction('actionBla');<br />
// handle the action using $this->getCurrentPlayerId()<br />
$this->gamestate->setPlayerNonMultiactive( $this->getCurrentPlayerId(), 'next');<br />
}<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; updating database manually<br />
: Use this helper function to change multiactive state without sending notification<br />
<pre><br />
/**<br />
* Changes values of multiactivity in db, does not sent notifications.<br />
* To send notifications after use updateMultiactiveOrNextState<br />
* @param number $player_id, player id <=0 or null - means ALL<br />
* @param number $value - 1 multiactive, 0 non multiactive<br />
*/<br />
function dbSetPlayerMultiactive($player_id = -1, $value = 1) {<br />
if (! $value)<br />
$value = 0;<br />
else<br />
$value = 1;<br />
$sql = "UPDATE player SET player_is_multiactive = '$value' WHERE player_zombie = 0 and player_eliminated = 0";<br />
if ($player_id > 0) {<br />
$sql .= " AND player_id = $player_id";<br />
}<br />
self::DbQuery($sql);<br />
}<br />
</pre><br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter is the name of the transition, and NOT the name of the target game state, see [[Your game state machine: states.inc.php]] for more information about states.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if the current player can perform a specific action in the current game state, and optionally throw an exception if they can't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game. It should not be called from methods where the current player is not necessarily the active player, otherwise it may fail with an "It is not your turn" exception.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
This is how PHP action looks that returns player to active state (only for multiplayeractive states). To be able to execute on js side do not checkAction on js side for this specific one.<br />
<br />
function actionUnpass() {<br />
$this->gamestate->checkPossibleAction('actionUnpass'); // player chane mind about passing while others were thinking<br />
$this->gamestate->setPlayersMultiactive(array ($this->getCurrentPlayerId() ), 'error', false);<br />
}<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table. However there no 0 index here.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
'''createNextPlayerTable( $players, $bLoop=true )'''<br />
<br />
Using $players array creates a map of current => next as in example from getNextPlayerTable(), however you can use custom order here. <br />
Parmeter $bLoop is true if last player points to first, false otherwise.<br />
In any case index 0 points to first player (first element of $players array). $players is array of player ids in desired order.<br />
<br />
N.B: This function '''DOES NOT''' allow to change players turn order!<br />
<br />
Example of usage:<br />
<pre><br />
function getNextPlayerTableCustom() {<br />
$starting = $this->getStartingPlayer(); // custom function to get starting player<br />
$player_ids = $this->getPlayerIdsInOrder($starting); // custom function to create players array starting from starting player<br />
return self::createNextPlayerTable($player_ids, false); // create next player table in custom order<br />
}<br />
</pre><br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translated.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.<br />
<br />
'''Important''': NO private data must be sent with this method, as a cheater could see it even if it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
'''Important''': When the game page is reloaded (i.e. F5 or when loading turn based game) all previous notifications are replayed as history notifications. These notifications do not trigger notification handlers and are used basically to build the game log. Because of that most of the notification arguments, except i18n, player_id and all arguments used in the message, are removed from these history notifications. If you need additional arguments in history notifications you can add special field <b>preserve</b> to notification arguments, like this:<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y,<br />
'preserve' => [ 'x', 'y' ]<br />
) );<br />
</pre><br />
In this example, fields x and y will be preserved when replaying history notification at the game load.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications<br />
* When you read log preview for old games its unreadable (this is log before you enter the game reply, useful for troubleshooting or game analysis)<br />
* Its more data to transfer and store in db<br />
* Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
Special handling of arguments:<br />
* ${player_name} - this will be wrapped in html and text shown using color of the corresponding player, some colors also have reserved background. This will apply recursively as well.<br />
* ${player_name2} - same <br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log. If you want a second player name in the log, name the variable ${player_name2}, etc.<br />
<br />
Note that spectators cannot be notified using this method, because their player ID is not available via loadPlayersBasicInfos() or otherwise. You must use notifyAllPlayers() for any notification that spectators should get.<br />
<br />
== About random and randomness ==<br />
<br />
A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dice and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
This is a BGA framework function that provides you a random number between "min" and "max" (inclusive), using the best available random method available on the system.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
As of now, bga_rand is based on the PHP function "random_int", which ensures a cryptographic level of randomness.<br />
<br />
In particular, it is '''mandatory''' to use it for all '''dice throw''' (ie: games using other methods for dice throwing will be rejected by BGA during review).<br />
<br />
Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
To shuffle items, like a pile of cards, the best way is to use the BGA PHP [[Deck]] component and to use "shuffle" method. This ensures that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.<br />
<br />
As of now, the Deck component shuffle method is based on PHP "shuffle" method, which has quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as they are based on PHP "shuffle" function (or similar, like "array_rand").<br />
<br />
=== Other methods ===<br />
<br />
Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistic is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you define statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
<br />
This method must be called for each statistic of your game, in your setupNewGame method.<br />
If you neglect to call this for a statistic, and also do not update the value during the course of a certain game using setStat or incStat, the value of the stat will be undefined rather than 0. This will result in it being ignored at the end of the game, as if it didn't apply to that particular game, and excluded from cumulative statistics. As a consequence - if do not want statistic to be applied, do not init it, or call set or inc on it.<br />
<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistic, or "player" if this is a player statistic.<br />
<br />
'$name' is the name of your statistic, as it has been defined in your stats.inc.php file.<br />
<br />
'$value' is the initial value of the statistic. If this is a player statistic and if the player is not specified by "$player_id" argument, the value is set for ALL players.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone win/lose together in a full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
Assign a score of zero to everyone if it's a loss.<br />
Assign the same score > 0 to everyone if it's a win.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone loses = everyone is tied. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.inc.php:<br />
<br />
<pre><br />
// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative or zero score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.<br />
<br />
This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* the player is informed in a dialog box that he no longer have to play and can start another game if he/she wants too (with buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.<br />
* When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
These functions should have been API but they are not, just add them to your php game and use for every game.<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see [[BGA Undo policy]]). Cannot use in multiactivate state or in game state where next state is multiactive.<br />
: Note: this function does not actually do anything when it is called, it only raises the flag to store the database AFTER transaction is over. So the actual state will be saved when you exit the function calling it (technically before first queued notification is sent, which matter if you transition to game state not to user state after), which may affect what you end up saved.<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
function actionUndo() {<br />
self::checkAction('actionUndo');<br />
$this->undoRestorePoint();<br />
$this->gamestate->nextState('next'); // transition to single player state (i.e. beginning of player actions for this turn)<br />
}<br />
<br />
== Managing errors and exceptions ==<br />
<br />
Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that they are not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: Base class to notify a system exception. The message will be hidden from the user, but show in the logs. Use this if the message contains technical information.<br />
: You shouldn't use this type of exception except if you think the information shown could be critical. Indeed: a generic error message will be shown to the user, so it's going to be difficult for you to see what happened.<br />
<br />
== Zombie mode ==<br />
<br />
When a player leaves a game for any reason (expelled, quit), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but this is cool if the other players can finish the game anyway. That's why zombie mode exists: allow the other player to finish the game, even if the situation is not ideal.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
* Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it. Trying to end the game is not supported by the framework and will likely cause unexpected errors.<br />
<br />
Most of the time, the best thing to do when it is zombie player turn is to jump immediately to a state where he is not active anymore. For example, if he is in a game state where he has a choice between playing A and playing B, the best thing to do is NOT to choose A or B, but to pass. So, even if there's no "pass" action in the rules, add a "zombiepass" transitition in your game state and use it.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
// NB: this parameter is used only to flag games supporting this feature; you must use (or not use) reattributeColorsBasedOnPreferences PHP method to actually enable or disable the feature.<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.<br />
* your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Legacy games API ==<br />
<br />
For some very specific games ("legacy", "campaign"), you need to keep some informations from a game to another.<br />
<br />
This should be an exceptional situation: the legacy API is costing resources on Board Game Arena databases, and is slowing down the game setup process + game end of game process. Please do not use it for things like:<br />
* keeping a player preference/settings (=> player preferences and game options should be used instead)<br />
* keeping a statistics, a score, or a ranking, while it is not planned in the physical board game, or while there is no added value compared to BGA statistics / rankings.<br />
<br />
You should use it for:<br />
* legacy games: when some components of the game has been altered in a previous game and should be kept as it is.<br />
* "campaign style" games: when a player is getting a "reward" at the end of a game, and should be able to use it in further games.<br />
<br />
Important: you cannot store more than 64k of data (serialized as JSON) per player per game. If you go over 64k, storeLegacyData function is going to FAIL, and there is a risk to create a major bug (= players blocked) in your game. You MUST make sure that no more than 64k of data is used for each player for your game. For example, if you are implementing a "campaign style" game and if you allow a player to start multiple campaign, you must LIMIT the number of different campaign so that the total data size to not go over the limit. We strongly recommend you to use this:<br />
<br />
try <br />
{<br />
$this->storeLegacyTeamData( 'my_variable', $my_data );<br />
}<br />
catch( feException $e )<br />
{<br />
if( $e->getCode() == FEX_legacy_size_exceeded )<br />
{<br />
// Do something here to free some space in Legacy data (ex: by removing some variables)<br />
}<br />
else<br />
throw $e;<br />
}<br />
<br />
The keys may only contain letters and numbers, underscore seems not to be allowed.<br />
<br />
; function storeLegacyData( $player_id, $key, $data, $ttl = 365 )<br />
: Store some data associated with $key for the given user / current game<br />
: In the opposite of all other game data, this data will PERSIST after the end of this table, and can be re-used<br />
: in a future table with the same game.<br />
: IMPORTANT: The only possible place where you can use this method is when the game is over at your table (last game action). Otherwise, there is a risk of conflicts between ongoing games. <br />
: TTL is a time-to-live: the maximum, and default, is 365 days.<br />
: In any way, the total data (= all keys) you can store for a given user+game is 64k (note: data is store serialized as JSON data)<br />
<br />
; function retrieveLegacyData( $player_id, $key )<br />
: Get data associated with $key for the current game<br />
: This data is common to ALL tables from the same game for this player, and persist from one table to another.<br />
: Note: calling this function has an important cost => please call it few times (possibly: only ONCE) for each player for 1 game if possible<br />
: Note: you can use '%' in $key to retrieve all keys matching the given patterns<br />
<br />
; function removeLegacyData( $player_id, $key )<br />
: Remove some legacy data with the given key<br />
: (useful to free some data to avoid going over 64k)<br />
<br />
<br />
<br />
; function storeLegacyTeamData( $key, $data, $ttl = 365 )<br />
: Same as storeLegacyData, except that it stores some data for the whole team within the current table<br />
: Ie: if players A, B and C are at a table, the legacy data will be saved for future table with (exactly) A, B and C on the table.<br />
: This is useful for games which are intended to be played several time by the same team.<br />
: Note: the data total size is still limited, so you must implement catch the FEX_legacy_size_exceeded exception if it happens<br />
<br />
; function retrieveLegacyTeamData($key)<br />
: Same as retrieveLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
; function removeLegacyTeamData()<br />
: Same as removeLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
== Language dependent games API ==<br />
<br />
This API is used for games that are heavily language dependent. Two most common use cases are:<br />
* Games that have a language dependent component that are not necessarily translatable, typically a list of words. (Think of games like Codenames, Decrypto, Just One...)<br />
* Games with massive communication where players would like to ensure that all participants speak the same language. (Think of games like Werewolf, The Resistance, maybe even dixit...)<br />
<br />
If this option is used, the table created will be limited only to users that have specific language in their profile. Player starting the game would be able to chose one of the languages he speaks.<br />
<br />
There is a new property language_dependency in gameinfos.inc.php which can be set like this:<br />
'language_dependecy' => false, //or if the property is missing, the game is not language dependent<br />
'language_dependecy' => true, //all players at the table must speak the same language<br />
'language_dependecy' => array( 1 => 'en', 2 => 'fr', 3 => 'it' ), //1-based list of supported languages<br />
<br />
In the gamename.game.php file, you can get the id of selected language with the method '''getGameLanguage'''.<br />
; function getGameLanguage()<br />
: Returns an index of the selected language as defined in gameinfos.inc.php.<br />
<br />
Languages currently available on BGA are:<br />
'ar' => array( 'name' => "العربية", 'code' => 'ar_AE' ), // Arabic<br />
'be' => array( 'name' => "беларуская мова", 'code' => 'be_BY' ), // Belarusian<br />
'bn' => array( 'name' => "বাংলা", 'code' => 'bn_BD' ), // Bengali<br />
'bg' => array( 'name' => "български език", 'code' => 'bg_BG' ), // Bulgarian<br />
'ca' => array( 'name' => "català", 'code' => 'ca_ES' ), // Catalan<br />
'cs' => array( 'name' => "čeština", 'code' => 'cs_CZ' ), // Czech<br />
'da' => array( 'name' => "dansk", 'code' => 'da_DK' ), // Danish<br />
'de' => array( 'name' => "deutsch", 'code' => 'de_DE' ), // German<br />
'el' => array( 'name' => "Ελληνικά", 'code' => 'el_GR' ), // Greek<br />
'en' => array( 'name' => "English", 'code' => 'en_US' ), // English<br />
'es' => array( 'name' => "español", 'code' => 'es_ES' ), // Spanish<br />
'et' => array( 'name' => "eesti keel", 'code' => 'et_EE' ), // Estonian <br />
'fi' => array( 'name' => "suomi", 'code' => 'fi_FI' ), // Finnish<br />
'fr' => array( 'name' => "français", 'code' => 'fr_FR' ), // French<br />
'he' => array( 'name' => "עברית", 'code' => 'he_IL' ), // Hebrew <br />
'hi' => array( 'name' => "हिन्दी", 'code' => 'hi_IN' ), // Hindi<br />
'hr' => array( 'name' => "Hrvatski", 'code' => 'hr_HR' ), // Croatian<br />
'hu' => array( 'name' => "magyar", 'code' => 'hu_HU' ), // Hungarian<br />
'id' => array( 'name' => "Bahasa Indonesia", 'code' => 'id_ID' ), // Indonesian<br />
'ms' => array( 'name' => "Bahasa Malaysia", 'code' => 'ms_MY' ), // Malaysian<br />
'it' => array( 'name' => "italiano", 'code' => 'it_IT' ), // Italian<br />
'ja' => array( 'name' => "日本語", 'code' => 'ja_JP' ), // Japanese<br />
'jv' => array( 'name' => "Basa Jawa", 'code' => 'jv_JV' ), // Javanese <br />
'ko' => array( 'name' => "한국어", 'code' => 'ko_KR' ), // Korean<br />
'lt' => array( 'name' => "lietuvių", 'code' => 'lt_LT' ), // Lithuanian<br />
'lv' => array( 'name' => "latviešu", 'code' => 'lv_LV' ), // Latvian<br />
'nl' => array( 'name' => "nederlands", 'code' => 'nl_NL' ), // Dutch<br />
'no' => array( 'name' => "norsk", 'code' => 'nb_NO' ), // Norwegian<br />
'oc' => array( 'name' => "occitan", 'code' => 'oc_FR' ), // Occitan<br />
'pl' => array( 'name' => "polski", 'code' => 'pl_PL' ), // Polish<br />
'pt' => array( 'name' => "português", 'code' => 'pt_PT' ), // Portuguese<br />
'ro' => array( 'name' => "română", 'code' => 'ro_RO' ), // Romanian<br />
'ru' => array( 'name' => "Русский язык", 'code' => 'ru_RU' ), // Russian<br />
'sk' => array( 'name' => "slovenčina", 'code' => 'sk_SK' ), // Slovak<br />
'sl' => array( 'name' => "slovenščina", 'code' => 'sl_SI' ), // Slovenian <br />
'sr' => array( 'name' => "Српски", 'code' => 'sr_RS' ), // Serbian <br />
'sv' => array( 'name' => "svenska", 'code' => 'sv_SE' ), // Swedish<br />
'tr' => array( 'name' => "Türkçe", 'code' => 'tr_TR' ), // Turkish <br />
'uk' => array( 'name' => "Українська мова", 'code' => 'uk_UA' ), // Ukrainian<br />
'zh' => array( 'name' => "中文 (漢)", 'code' => 'zh_TW' ), // Traditional Chinese (Hong Kong, Macau, Taiwan)<br />
'zh-cn' => array( 'name' => "中文 (汉)", 'code' => 'zh_CN' ), // Simplified Chinese (Mainland China, Singapore)<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=8472
Main game logic: yourgamename.game.php
2021-06-08T02:28:33Z
<p>JonChambers: /* Notify players */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* Constructor: where you define global variables.<br />
* setupNewGame: initial setup of the game. Takes an array of players, indexed by player_id. Structure of each player includes player_name, player_canal, player_avatar, and flags indicating admin/ai/premium/order/language/beginner.<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game. Returned value must include ['players'][playerId]['score'] for scores to populate when F5 is pressed.<br />
* getGameProgression: where you compute the game progression indicator. Returns a number indicating percent of progression (0-100)<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions ([https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php more info here]). <br />
* Game state arguments: methods to return additional data on specific game states ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#args more info here]).<br />
* Game state actions: the logic to run when entering a new game state ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#action more info here]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
* upgradeTableDb: function to migrate database if you change it after release on production.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
; isSpectator()<br />
: Check the "current_player" spectator status. If true, the user accessing the game is a spectator (not part of the game). For this user, the interface should display all public information, and no private information (like a friend sitting at the same table as players and just spectating the game).<br />
<br />
; getActivePlayerColor()<br />
: This function does not seems to exist in API, if you need it here is implementation<br />
function getActivePlayerColor() {<br />
$player_id = self::getActivePlayerId();<br />
$players = self::loadPlayersBasicInfos();<br />
if( isset( $players[ $player_id ]) )<br />
return $players[ $player_id ]['player_color'];<br />
else<br />
return null;<br />
}<br />
<br />
== Accessing the database ==<br />
<br />
The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.<br />
<br />
'''IMPORTANT'''<br />
<br />
BGA uses [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. This means that your database changes WON'T BE APPLIED to the database until your request ends normally (web request, not database request). Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed. This also means that you need not (and in fact cannot) use your own transactions for multiple related database operations.<br />
<br />
However there sets of database operation that initite implicit commit (most common mistake is to use "TRUNCATE"), you cannot use these operations during the game, it breaks the unrolling of transactions and will lead to nasty issues<br />
(https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit).<br />
<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE/INSERT query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE/INSERT queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array is an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Same as getCollectionFromDB($sdl), but raise an exception if the collection is empty. Note: this function does NOT have 2nd argument as previous one does.<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Similar to previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: The result is the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of __construct() method of ''yourgamename.game.php''. This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 80 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multipleactiveplayer"<br />
: Note: avoid using this method in a "multipleactiveplayer" state because it does not mean anything.<br />
<br />
=== Multiple activate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (this will trigger '''onUpdateActionButtons''').<br />
: Usually, you use this method at the beginning of a game state (e.g., "stGameState") which transitions to a ''multipleactiveplayer'' state in which multiple players have to perform some action. Do not use this method if you going to make some more changes in the active player list. (I.e., if you want to take away multipleactiveplayer status immediately afterwards, use setPlayersMultiactive instead.)<br />
<br />
Example of usage:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
<br />
And this is the state declaration:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'multiPlayerDoSomething',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "actionBla" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players whose state changed.<br />
: "players" is the array of player id that should be made active.<br />
: If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action. It is also possible to call it directly from multiplayer action handler.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
Example of usage (see state declaration of playerTurnPlace above):<br />
<pre><br />
function actionBla($args) {<br />
self::checkAction('actionBla');<br />
// handle the action using $this->getCurrentPlayerId()<br />
$this->gamestate->setPlayerNonMultiactive( $this->getCurrentPlayerId(), 'next');<br />
}<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; updating database manually<br />
: Use this helper function to change multiactive state without sending notification<br />
<pre><br />
/**<br />
* Changes values of multiactivity in db, does not sent notifications.<br />
* To send notifications after use updateMultiactiveOrNextState<br />
* @param number $player_id, player id <=0 or null - means ALL<br />
* @param number $value - 1 multiactive, 0 non multiactive<br />
*/<br />
function dbSetPlayerMultiactive($player_id = -1, $value = 1) {<br />
if (! $value)<br />
$value = 0;<br />
else<br />
$value = 1;<br />
$sql = "UPDATE player SET player_is_multiactive = '$value' WHERE player_zombie = 0 and player_eliminated = 0";<br />
if ($player_id > 0) {<br />
$sql .= " AND player_id = $player_id";<br />
}<br />
self::DbQuery($sql);<br />
}<br />
</pre><br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter is the name of the transition, and NOT the name of the target game state, see [[Your game state machine: states.inc.php]] for more information about states.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if the current player can perform a specific action in the current game state, and optionally throw an exception if they can't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game. It should not be called from methods where the current player is not necessarily the active player, otherwise it may fail with an "It is not your turn" exception.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
This is how PHP action looks that returns player to active state (only for multiplayeractive states). To be able to execute on js side do not checkAction on js side for this specific one.<br />
<br />
function actionUnpass() {<br />
$this->gamestate->checkPossibleAction('actionUnpass'); // player chane mind about passing while others were thinking<br />
$this->gamestate->setPlayersMultiactive(array ($this->getCurrentPlayerId() ), 'error', false);<br />
}<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table. However there no 0 index here.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
'''createNextPlayerTable( $players, $bLoop=true )'''<br />
<br />
Using $players array creates a map of current => next as in example from getNextPlayerTable(), however you can use custom order here. <br />
Parmeter $bLoop is true if last player points to first, false otherwise.<br />
In any case index 0 points to first player (first element of $players array). $players is array of player ids in desired order.<br />
<br />
N.B: This function '''DOES NOT''' allow to change players turn order!<br />
<br />
Example of usage:<br />
<pre><br />
function getNextPlayerTableCustom() {<br />
$starting = $this->getStartingPlayer(); // custom function to get starting player<br />
$player_ids = $this->getPlayerIdsInOrder($starting); // custom function to create players array starting from starting player<br />
return self::createNextPlayerTable($player_ids, false); // create next player table in custom order<br />
}<br />
</pre><br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translated.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.<br />
<br />
'''Important''': NO private data must be sent with this method, as a cheater could see it even if it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
'''Important''': When the game page is reloaded (i.e. F5 or when loading turn based game) all previous notifications are replayed as history notifications. These notifications do not trigger notification handlers and are used basically to build the game log. Because of that most of the notification arguments, expect i18n, player_id and all arguments used in the message, are removed from these history notifications. If you need additional arguments in history notifications you can add special field <b>preserve</b> to notification arguments, like this:<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y,<br />
'preserve' => [ 'x', 'y' ]<br />
) );<br />
</pre><br />
In this example, fields x and y will be preserved when replaying history notification at the game load.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications<br />
* When you read log preview for old games its unreadable (this is log before you enter the game reply, useful for troubleshooting or game analysis)<br />
* Its more data to transfer and store in db<br />
* Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
Special handling of arguments:<br />
* ${player_name} - this will be wrapped in html and text shown using color of the corresponding player, some colors also have reserved background. This will apply recursively as well.<br />
* ${player_name2} - same <br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log. If you want a second player name in the log, name the variable ${player_name2}, etc.<br />
<br />
Note that spectators cannot be notified using this method, because their player ID is not available via loadPlayersBasicInfos() or otherwise. You must use notifyAllPlayers() for any notification that spectators should get.<br />
<br />
== About random and randomness ==<br />
<br />
A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dice and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
This is a BGA framework function that provides you a random number between "min" and "max" (inclusive), using the best available random method available on the system.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
As of now, bga_rand is based on the PHP function "random_int", which ensures a cryptographic level of randomness.<br />
<br />
In particular, it is '''mandatory''' to use it for all '''dice throw''' (ie: games using other methods for dice throwing will be rejected by BGA during review).<br />
<br />
Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
To shuffle items, like a pile of cards, the best way is to use the BGA PHP [[Deck]] component and to use "shuffle" method. This ensures that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.<br />
<br />
As of now, the Deck component shuffle method is based on PHP "shuffle" method, which has quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as they are based on PHP "shuffle" function (or similar, like "array_rand").<br />
<br />
=== Other methods ===<br />
<br />
Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistic is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you define statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
<br />
This method must be called for each statistic of your game, in your setupNewGame method.<br />
If you neglect to call this for a statistic, and also do not update the value during the course of a certain game using setStat or incStat, the value of the stat will be undefined rather than 0. This will result in it being ignored at the end of the game, as if it didn't apply to that particular game, and excluded from cumulative statistics. As a consequence - if do not want statistic to be applied, do not init it, or call set or inc on it.<br />
<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistic, or "player" if this is a player statistic.<br />
<br />
'$name' is the name of your statistic, as it has been defined in your stats.inc.php file.<br />
<br />
'$value' is the initial value of the statistic. If this is a player statistic and if the player is not specified by "$player_id" argument, the value is set for ALL players.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone win/lose together in a full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
Assign a score of zero to everyone if it's a loss.<br />
Assign the same score > 0 to everyone if it's a win.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone loses = everyone is tied. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.inc.php:<br />
<br />
<pre><br />
// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative or zero score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.<br />
<br />
This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* the player is informed in a dialog box that he no longer have to play and can start another game if he/she wants too (with buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.<br />
* When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
These functions should have been API but they are not, just add them to your php game and use for every game.<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see [[BGA Undo policy]]). Cannot use in multiactivate state or in game state where next state is multiactive.<br />
: Note: this function does not actually do anything when it is called, it only raises the flag to store the database AFTER transaction is over. So the actual state will be saved when you exit the function calling it (technically before first queued notification is sent, which matter if you transition to game state not to user state after), which may affect what you end up saved.<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
function actionUndo() {<br />
self::checkAction('actionUndo');<br />
$this->undoRestorePoint();<br />
$this->gamestate->nextState('next'); // transition to single player state (i.e. beginning of player actions for this turn)<br />
}<br />
<br />
== Managing errors and exceptions ==<br />
<br />
Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that they are not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: Base class to notify a system exception. The message will be hidden from the user, but show in the logs. Use this if the message contains technical information.<br />
: You shouldn't use this type of exception except if you think the information shown could be critical. Indeed: a generic error message will be shown to the user, so it's going to be difficult for you to see what happened.<br />
<br />
== Zombie mode ==<br />
<br />
When a player leaves a game for any reason (expelled, quit), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but this is cool if the other players can finish the game anyway. That's why zombie mode exists: allow the other player to finish the game, even if the situation is not ideal.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
* Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it. Trying to end the game is not supported by the framework and will likely cause unexpected errors.<br />
<br />
Most of the time, the best thing to do when it is zombie player turn is to jump immediately to a state where he is not active anymore. For example, if he is in a game state where he has a choice between playing A and playing B, the best thing to do is NOT to choose A or B, but to pass. So, even if there's no "pass" action in the rules, add a "zombiepass" transitition in your game state and use it.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
// NB: this parameter is used only to flag games supporting this feature; you must use (or not use) reattributeColorsBasedOnPreferences PHP method to actually enable or disable the feature.<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.<br />
* your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Legacy games API ==<br />
<br />
For some very specific games ("legacy", "campaign"), you need to keep some informations from a game to another.<br />
<br />
This should be an exceptional situation: the legacy API is costing resources on Board Game Arena databases, and is slowing down the game setup process + game end of game process. Please do not use it for things like:<br />
* keeping a player preference/settings (=> player preferences and game options should be used instead)<br />
* keeping a statistics, a score, or a ranking, while it is not planned in the physical board game, or while there is no added value compared to BGA statistics / rankings.<br />
<br />
You should use it for:<br />
* legacy games: when some components of the game has been altered in a previous game and should be kept as it is.<br />
* "campaign style" games: when a player is getting a "reward" at the end of a game, and should be able to use it in further games.<br />
<br />
Important: you cannot store more than 64k of data (serialized as JSON) per player per game. If you go over 64k, storeLegacyData function is going to FAIL, and there is a risk to create a major bug (= players blocked) in your game. You MUST make sure that no more than 64k of data is used for each player for your game. For example, if you are implementing a "campaign style" game and if you allow a player to start multiple campaign, you must LIMIT the number of different campaign so that the total data size to not go over the limit. We strongly recommend you to use this:<br />
<br />
try <br />
{<br />
$this->storeLegacyTeamData( 'my_variable', $my_data );<br />
}<br />
catch( feException $e )<br />
{<br />
if( $e->getCode() == FEX_legacy_size_exceeded )<br />
{<br />
// Do something here to free some space in Legacy data (ex: by removing some variables)<br />
}<br />
else<br />
throw $e;<br />
}<br />
<br />
The keys may only contain letters and numbers, underscore seems not to be allowed.<br />
<br />
; function storeLegacyData( $player_id, $key, $data, $ttl = 365 )<br />
: Store some data associated with $key for the given user / current game<br />
: In the opposite of all other game data, this data will PERSIST after the end of this table, and can be re-used<br />
: in a future table with the same game.<br />
: IMPORTANT: The only possible place where you can use this method is when the game is over at your table (last game action). Otherwise, there is a risk of conflicts between ongoing games. <br />
: TTL is a time-to-live: the maximum, and default, is 365 days.<br />
: In any way, the total data (= all keys) you can store for a given user+game is 64k (note: data is store serialized as JSON data)<br />
<br />
; function retrieveLegacyData( $player_id, $key )<br />
: Get data associated with $key for the current game<br />
: This data is common to ALL tables from the same game for this player, and persist from one table to another.<br />
: Note: calling this function has an important cost => please call it few times (possibly: only ONCE) for each player for 1 game if possible<br />
: Note: you can use '%' in $key to retrieve all keys matching the given patterns<br />
<br />
; function removeLegacyData( $player_id, $key )<br />
: Remove some legacy data with the given key<br />
: (useful to free some data to avoid going over 64k)<br />
<br />
<br />
<br />
; function storeLegacyTeamData( $key, $data, $ttl = 365 )<br />
: Same as storeLegacyData, except that it stores some data for the whole team within the current table<br />
: Ie: if players A, B and C are at a table, the legacy data will be saved for future table with (exactly) A, B and C on the table.<br />
: This is useful for games which are intended to be played several time by the same team.<br />
: Note: the data total size is still limited, so you must implement catch the FEX_legacy_size_exceeded exception if it happens<br />
<br />
; function retrieveLegacyTeamData($key)<br />
: Same as retrieveLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
; function removeLegacyTeamData()<br />
: Same as removeLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
== Language dependent games API ==<br />
<br />
This API is used for games that are heavily language dependent. Two most common use cases are:<br />
* Games that have a language dependent component that are not necessarily translatable, typically a list of words. (Think of games like Codenames, Decrypto, Just One...)<br />
* Games with massive communication where players would like to ensure that all participants speak the same language. (Think of games like Werewolf, The Resistance, maybe even dixit...)<br />
<br />
If this option is used, the table created will be limited only to users that have specific language in their profile. Player starting the game would be able to chose one of the languages he speaks.<br />
<br />
There is a new property language_dependency in gameinfos.inc.php which can be set like this:<br />
'language_dependecy' => false, //or if the property is missing, the game is not language dependent<br />
'language_dependecy' => true, //all players at the table must speak the same language<br />
'language_dependecy' => array( 1 => 'en', 2 => 'fr', 3 => 'it' ), //1-based list of supported languages<br />
<br />
In the gamename.game.php file, you can get the id of selected language with the method '''getGameLanguage'''.<br />
; function getGameLanguage()<br />
: Returns an index of the selected language as defined in gameinfos.inc.php.<br />
<br />
Languages currently available on BGA are:<br />
'ar' => array( 'name' => "العربية", 'code' => 'ar_AE' ), // Arabic<br />
'be' => array( 'name' => "беларуская мова", 'code' => 'be_BY' ), // Belarusian<br />
'bn' => array( 'name' => "বাংলা", 'code' => 'bn_BD' ), // Bengali<br />
'bg' => array( 'name' => "български език", 'code' => 'bg_BG' ), // Bulgarian<br />
'ca' => array( 'name' => "català", 'code' => 'ca_ES' ), // Catalan<br />
'cs' => array( 'name' => "čeština", 'code' => 'cs_CZ' ), // Czech<br />
'da' => array( 'name' => "dansk", 'code' => 'da_DK' ), // Danish<br />
'de' => array( 'name' => "deutsch", 'code' => 'de_DE' ), // German<br />
'el' => array( 'name' => "Ελληνικά", 'code' => 'el_GR' ), // Greek<br />
'en' => array( 'name' => "English", 'code' => 'en_US' ), // English<br />
'es' => array( 'name' => "español", 'code' => 'es_ES' ), // Spanish<br />
'et' => array( 'name' => "eesti keel", 'code' => 'et_EE' ), // Estonian <br />
'fi' => array( 'name' => "suomi", 'code' => 'fi_FI' ), // Finnish<br />
'fr' => array( 'name' => "français", 'code' => 'fr_FR' ), // French<br />
'he' => array( 'name' => "עברית", 'code' => 'he_IL' ), // Hebrew <br />
'hi' => array( 'name' => "हिन्दी", 'code' => 'hi_IN' ), // Hindi<br />
'hr' => array( 'name' => "Hrvatski", 'code' => 'hr_HR' ), // Croatian<br />
'hu' => array( 'name' => "magyar", 'code' => 'hu_HU' ), // Hungarian<br />
'id' => array( 'name' => "Bahasa Indonesia", 'code' => 'id_ID' ), // Indonesian<br />
'ms' => array( 'name' => "Bahasa Malaysia", 'code' => 'ms_MY' ), // Malaysian<br />
'it' => array( 'name' => "italiano", 'code' => 'it_IT' ), // Italian<br />
'ja' => array( 'name' => "日本語", 'code' => 'ja_JP' ), // Japanese<br />
'jv' => array( 'name' => "Basa Jawa", 'code' => 'jv_JV' ), // Javanese <br />
'ko' => array( 'name' => "한국어", 'code' => 'ko_KR' ), // Korean<br />
'lt' => array( 'name' => "lietuvių", 'code' => 'lt_LT' ), // Lithuanian<br />
'lv' => array( 'name' => "latviešu", 'code' => 'lv_LV' ), // Latvian<br />
'nl' => array( 'name' => "nederlands", 'code' => 'nl_NL' ), // Dutch<br />
'no' => array( 'name' => "norsk", 'code' => 'nb_NO' ), // Norwegian<br />
'oc' => array( 'name' => "occitan", 'code' => 'oc_FR' ), // Occitan<br />
'pl' => array( 'name' => "polski", 'code' => 'pl_PL' ), // Polish<br />
'pt' => array( 'name' => "português", 'code' => 'pt_PT' ), // Portuguese<br />
'ro' => array( 'name' => "română", 'code' => 'ro_RO' ), // Romanian<br />
'ru' => array( 'name' => "Русский язык", 'code' => 'ru_RU' ), // Russian<br />
'sk' => array( 'name' => "slovenčina", 'code' => 'sk_SK' ), // Slovak<br />
'sl' => array( 'name' => "slovenščina", 'code' => 'sl_SI' ), // Slovenian <br />
'sr' => array( 'name' => "Српски", 'code' => 'sr_RS' ), // Serbian <br />
'sv' => array( 'name' => "svenska", 'code' => 'sv_SE' ), // Swedish<br />
'tr' => array( 'name' => "Türkçe", 'code' => 'tr_TR' ), // Turkish <br />
'uk' => array( 'name' => "Українська мова", 'code' => 'uk_UA' ), // Ukrainian<br />
'zh' => array( 'name' => "中文 (漢)", 'code' => 'zh_TW' ), // Traditional Chinese (Hong Kong, Macau, Taiwan)<br />
'zh-cn' => array( 'name' => "中文 (汉)", 'code' => 'zh_CN' ), // Simplified Chinese (Mainland China, Singapore)<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=8471
Main game logic: yourgamename.game.php
2021-06-08T02:23:52Z
<p>JonChambers: /* Notify players */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* Constructor: where you define global variables.<br />
* setupNewGame: initial setup of the game. Takes an array of players, indexed by player_id. Structure of each player includes player_name, player_canal, player_avatar, and flags indicating admin/ai/premium/order/language/beginner.<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game. Returned value must include ['players'][playerId]['score'] for scores to populate when F5 is pressed.<br />
* getGameProgression: where you compute the game progression indicator. Returns a number indicating percent of progression (0-100)<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions ([https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php more info here]). <br />
* Game state arguments: methods to return additional data on specific game states ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#args more info here]).<br />
* Game state actions: the logic to run when entering a new game state ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#action more info here]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
* upgradeTableDb: function to migrate database if you change it after release on production.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
; isSpectator()<br />
: Check the "current_player" spectator status. If true, the user accessing the game is a spectator (not part of the game). For this user, the interface should display all public information, and no private information (like a friend sitting at the same table as players and just spectating the game).<br />
<br />
; getActivePlayerColor()<br />
: This function does not seems to exist in API, if you need it here is implementation<br />
function getActivePlayerColor() {<br />
$player_id = self::getActivePlayerId();<br />
$players = self::loadPlayersBasicInfos();<br />
if( isset( $players[ $player_id ]) )<br />
return $players[ $player_id ]['player_color'];<br />
else<br />
return null;<br />
}<br />
<br />
== Accessing the database ==<br />
<br />
The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.<br />
<br />
'''IMPORTANT'''<br />
<br />
BGA uses [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. This means that your database changes WON'T BE APPLIED to the database until your request ends normally (web request, not database request). Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed. This also means that you need not (and in fact cannot) use your own transactions for multiple related database operations.<br />
<br />
However there sets of database operation that initite implicit commit (most common mistake is to use "TRUNCATE"), you cannot use these operations during the game, it breaks the unrolling of transactions and will lead to nasty issues<br />
(https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit).<br />
<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE/INSERT query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE/INSERT queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array is an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Same as getCollectionFromDB($sdl), but raise an exception if the collection is empty. Note: this function does NOT have 2nd argument as previous one does.<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Similar to previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: The result is the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of __construct() method of ''yourgamename.game.php''. This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 80 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multipleactiveplayer"<br />
: Note: avoid using this method in a "multipleactiveplayer" state because it does not mean anything.<br />
<br />
=== Multiple activate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (this will trigger '''onUpdateActionButtons''').<br />
: Usually, you use this method at the beginning of a game state (e.g., "stGameState") which transitions to a ''multipleactiveplayer'' state in which multiple players have to perform some action. Do not use this method if you going to make some more changes in the active player list. (I.e., if you want to take away multipleactiveplayer status immediately afterwards, use setPlayersMultiactive instead.)<br />
<br />
Example of usage:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
<br />
And this is the state declaration:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'multiPlayerDoSomething',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "actionBla" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players whose state changed.<br />
: "players" is the array of player id that should be made active.<br />
: If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action. It is also possible to call it directly from multiplayer action handler.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
Example of usage (see state declaration of playerTurnPlace above):<br />
<pre><br />
function actionBla($args) {<br />
self::checkAction('actionBla');<br />
// handle the action using $this->getCurrentPlayerId()<br />
$this->gamestate->setPlayerNonMultiactive( $this->getCurrentPlayerId(), 'next');<br />
}<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; updating database manually<br />
: Use this helper function to change multiactive state without sending notification<br />
<pre><br />
/**<br />
* Changes values of multiactivity in db, does not sent notifications.<br />
* To send notifications after use updateMultiactiveOrNextState<br />
* @param number $player_id, player id <=0 or null - means ALL<br />
* @param number $value - 1 multiactive, 0 non multiactive<br />
*/<br />
function dbSetPlayerMultiactive($player_id = -1, $value = 1) {<br />
if (! $value)<br />
$value = 0;<br />
else<br />
$value = 1;<br />
$sql = "UPDATE player SET player_is_multiactive = '$value' WHERE player_zombie = 0 and player_eliminated = 0";<br />
if ($player_id > 0) {<br />
$sql .= " AND player_id = $player_id";<br />
}<br />
self::DbQuery($sql);<br />
}<br />
</pre><br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter is the name of the transition, and NOT the name of the target game state, see [[Your game state machine: states.inc.php]] for more information about states.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if the current player can perform a specific action in the current game state, and optionally throw an exception if they can't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game. It should not be called from methods where the current player is not necessarily the active player, otherwise it may fail with an "It is not your turn" exception.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
This is how PHP action looks that returns player to active state (only for multiplayeractive states). To be able to execute on js side do not checkAction on js side for this specific one.<br />
<br />
function actionUnpass() {<br />
$this->gamestate->checkPossibleAction('actionUnpass'); // player chane mind about passing while others were thinking<br />
$this->gamestate->setPlayersMultiactive(array ($this->getCurrentPlayerId() ), 'error', false);<br />
}<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table. However there no 0 index here.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
'''createNextPlayerTable( $players, $bLoop=true )'''<br />
<br />
Using $players array creates a map of current => next as in example from getNextPlayerTable(), however you can use custom order here. <br />
Parmeter $bLoop is true if last player points to first, false otherwise.<br />
In any case index 0 points to first player (first element of $players array). $players is array of player ids in desired order.<br />
<br />
N.B: This function '''DOES NOT''' allow to change players turn order!<br />
<br />
Example of usage:<br />
<pre><br />
function getNextPlayerTableCustom() {<br />
$starting = $this->getStartingPlayer(); // custom function to get starting player<br />
$player_ids = $this->getPlayerIdsInOrder($starting); // custom function to create players array starting from starting player<br />
return self::createNextPlayerTable($player_ids, false); // create next player table in custom order<br />
}<br />
</pre><br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translated.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.<br />
<br />
'''Important''': NO private data must be sent with this method, as a cheater could see it even it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
'''Important''': When the game page is reloaded (i.e. F5 or when loading turn based game) all previous notifications are replayed as history notifications. These notifications do not trigger notification handlers and are used basically to build the game log. Because of that most of the notification arguments, expect i18n, player_id and all arguments used in the message, are removed from these history notifications. If you need additional arguments in history notifications you can add special field <b>preserve</b> to notification arguments, like this:<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y,<br />
'preserve' => [ 'x', 'y' ]<br />
) );<br />
</pre><br />
In this example, fields x and y will be preserved when replaying history notification at the game load.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications<br />
* When you read log preview for old games its unreadable (this is log before you enter the game reply, useful for troubleshooting or game analysis)<br />
* Its more data to transfer and store in db<br />
* Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
Special handling of arguments:<br />
* ${player_name} - this will be wrapped in html and text shown using color of the corresponding player, some colors also have reserved background. This will apply recursively as well.<br />
* ${player_name2} - same <br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log. If you want a second player name in the log, name the variable ${player_name2}, etc.<br />
<br />
Note that spectators cannot be notified using this method, because their player ID is not available via loadPlayersBasicInfos() or otherwise. You must use notifyAllPlayers() for any notification that spectators should get.<br />
<br />
== About random and randomness ==<br />
<br />
A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dice and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
This is a BGA framework function that provides you a random number between "min" and "max" (inclusive), using the best available random method available on the system.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
As of now, bga_rand is based on the PHP function "random_int", which ensures a cryptographic level of randomness.<br />
<br />
In particular, it is '''mandatory''' to use it for all '''dice throw''' (ie: games using other methods for dice throwing will be rejected by BGA during review).<br />
<br />
Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
To shuffle items, like a pile of cards, the best way is to use the BGA PHP [[Deck]] component and to use "shuffle" method. This ensures that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.<br />
<br />
As of now, the Deck component shuffle method is based on PHP "shuffle" method, which has quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as they are based on PHP "shuffle" function (or similar, like "array_rand").<br />
<br />
=== Other methods ===<br />
<br />
Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistic is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you define statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
<br />
This method must be called for each statistic of your game, in your setupNewGame method.<br />
If you neglect to call this for a statistic, and also do not update the value during the course of a certain game using setStat or incStat, the value of the stat will be undefined rather than 0. This will result in it being ignored at the end of the game, as if it didn't apply to that particular game, and excluded from cumulative statistics. As a consequence - if do not want statistic to be applied, do not init it, or call set or inc on it.<br />
<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistic, or "player" if this is a player statistic.<br />
<br />
'$name' is the name of your statistic, as it has been defined in your stats.inc.php file.<br />
<br />
'$value' is the initial value of the statistic. If this is a player statistic and if the player is not specified by "$player_id" argument, the value is set for ALL players.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone win/lose together in a full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
Assign a score of zero to everyone if it's a loss.<br />
Assign the same score > 0 to everyone if it's a win.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone loses = everyone is tied. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.inc.php:<br />
<br />
<pre><br />
// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative or zero score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.<br />
<br />
This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* the player is informed in a dialog box that he no longer have to play and can start another game if he/she wants too (with buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.<br />
* When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
These functions should have been API but they are not, just add them to your php game and use for every game.<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see [[BGA Undo policy]]). Cannot use in multiactivate state or in game state where next state is multiactive.<br />
: Note: this function does not actually do anything when it is called, it only raises the flag to store the database AFTER transaction is over. So the actual state will be saved when you exit the function calling it (technically before first queued notification is sent, which matter if you transition to game state not to user state after), which may affect what you end up saved.<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
function actionUndo() {<br />
self::checkAction('actionUndo');<br />
$this->undoRestorePoint();<br />
$this->gamestate->nextState('next'); // transition to single player state (i.e. beginning of player actions for this turn)<br />
}<br />
<br />
== Managing errors and exceptions ==<br />
<br />
Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that they are not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: Base class to notify a system exception. The message will be hidden from the user, but show in the logs. Use this if the message contains technical information.<br />
: You shouldn't use this type of exception except if you think the information shown could be critical. Indeed: a generic error message will be shown to the user, so it's going to be difficult for you to see what happened.<br />
<br />
== Zombie mode ==<br />
<br />
When a player leaves a game for any reason (expelled, quit), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but this is cool if the other players can finish the game anyway. That's why zombie mode exists: allow the other player to finish the game, even if the situation is not ideal.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
* Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it. Trying to end the game is not supported by the framework and will likely cause unexpected errors.<br />
<br />
Most of the time, the best thing to do when it is zombie player turn is to jump immediately to a state where he is not active anymore. For example, if he is in a game state where he has a choice between playing A and playing B, the best thing to do is NOT to choose A or B, but to pass. So, even if there's no "pass" action in the rules, add a "zombiepass" transitition in your game state and use it.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
// NB: this parameter is used only to flag games supporting this feature; you must use (or not use) reattributeColorsBasedOnPreferences PHP method to actually enable or disable the feature.<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.<br />
* your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Legacy games API ==<br />
<br />
For some very specific games ("legacy", "campaign"), you need to keep some informations from a game to another.<br />
<br />
This should be an exceptional situation: the legacy API is costing resources on Board Game Arena databases, and is slowing down the game setup process + game end of game process. Please do not use it for things like:<br />
* keeping a player preference/settings (=> player preferences and game options should be used instead)<br />
* keeping a statistics, a score, or a ranking, while it is not planned in the physical board game, or while there is no added value compared to BGA statistics / rankings.<br />
<br />
You should use it for:<br />
* legacy games: when some components of the game has been altered in a previous game and should be kept as it is.<br />
* "campaign style" games: when a player is getting a "reward" at the end of a game, and should be able to use it in further games.<br />
<br />
Important: you cannot store more than 64k of data (serialized as JSON) per player per game. If you go over 64k, storeLegacyData function is going to FAIL, and there is a risk to create a major bug (= players blocked) in your game. You MUST make sure that no more than 64k of data is used for each player for your game. For example, if you are implementing a "campaign style" game and if you allow a player to start multiple campaign, you must LIMIT the number of different campaign so that the total data size to not go over the limit. We strongly recommend you to use this:<br />
<br />
try <br />
{<br />
$this->storeLegacyTeamData( 'my_variable', $my_data );<br />
}<br />
catch( feException $e )<br />
{<br />
if( $e->getCode() == FEX_legacy_size_exceeded )<br />
{<br />
// Do something here to free some space in Legacy data (ex: by removing some variables)<br />
}<br />
else<br />
throw $e;<br />
}<br />
<br />
The keys may only contain letters and numbers, underscore seems not to be allowed.<br />
<br />
; function storeLegacyData( $player_id, $key, $data, $ttl = 365 )<br />
: Store some data associated with $key for the given user / current game<br />
: In the opposite of all other game data, this data will PERSIST after the end of this table, and can be re-used<br />
: in a future table with the same game.<br />
: IMPORTANT: The only possible place where you can use this method is when the game is over at your table (last game action). Otherwise, there is a risk of conflicts between ongoing games. <br />
: TTL is a time-to-live: the maximum, and default, is 365 days.<br />
: In any way, the total data (= all keys) you can store for a given user+game is 64k (note: data is store serialized as JSON data)<br />
<br />
; function retrieveLegacyData( $player_id, $key )<br />
: Get data associated with $key for the current game<br />
: This data is common to ALL tables from the same game for this player, and persist from one table to another.<br />
: Note: calling this function has an important cost => please call it few times (possibly: only ONCE) for each player for 1 game if possible<br />
: Note: you can use '%' in $key to retrieve all keys matching the given patterns<br />
<br />
; function removeLegacyData( $player_id, $key )<br />
: Remove some legacy data with the given key<br />
: (useful to free some data to avoid going over 64k)<br />
<br />
<br />
<br />
; function storeLegacyTeamData( $key, $data, $ttl = 365 )<br />
: Same as storeLegacyData, except that it stores some data for the whole team within the current table<br />
: Ie: if players A, B and C are at a table, the legacy data will be saved for future table with (exactly) A, B and C on the table.<br />
: This is useful for games which are intended to be played several time by the same team.<br />
: Note: the data total size is still limited, so you must implement catch the FEX_legacy_size_exceeded exception if it happens<br />
<br />
; function retrieveLegacyTeamData($key)<br />
: Same as retrieveLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
; function removeLegacyTeamData()<br />
: Same as removeLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
== Language dependent games API ==<br />
<br />
This API is used for games that are heavily language dependent. Two most common use cases are:<br />
* Games that have a language dependent component that are not necessarily translatable, typically a list of words. (Think of games like Codenames, Decrypto, Just One...)<br />
* Games with massive communication where players would like to ensure that all participants speak the same language. (Think of games like Werewolf, The Resistance, maybe even dixit...)<br />
<br />
If this option is used, the table created will be limited only to users that have specific language in their profile. Player starting the game would be able to chose one of the languages he speaks.<br />
<br />
There is a new property language_dependency in gameinfos.inc.php which can be set like this:<br />
'language_dependecy' => false, //or if the property is missing, the game is not language dependent<br />
'language_dependecy' => true, //all players at the table must speak the same language<br />
'language_dependecy' => array( 1 => 'en', 2 => 'fr', 3 => 'it' ), //1-based list of supported languages<br />
<br />
In the gamename.game.php file, you can get the id of selected language with the method '''getGameLanguage'''.<br />
; function getGameLanguage()<br />
: Returns an index of the selected language as defined in gameinfos.inc.php.<br />
<br />
Languages currently available on BGA are:<br />
'ar' => array( 'name' => "العربية", 'code' => 'ar_AE' ), // Arabic<br />
'be' => array( 'name' => "беларуская мова", 'code' => 'be_BY' ), // Belarusian<br />
'bn' => array( 'name' => "বাংলা", 'code' => 'bn_BD' ), // Bengali<br />
'bg' => array( 'name' => "български език", 'code' => 'bg_BG' ), // Bulgarian<br />
'ca' => array( 'name' => "català", 'code' => 'ca_ES' ), // Catalan<br />
'cs' => array( 'name' => "čeština", 'code' => 'cs_CZ' ), // Czech<br />
'da' => array( 'name' => "dansk", 'code' => 'da_DK' ), // Danish<br />
'de' => array( 'name' => "deutsch", 'code' => 'de_DE' ), // German<br />
'el' => array( 'name' => "Ελληνικά", 'code' => 'el_GR' ), // Greek<br />
'en' => array( 'name' => "English", 'code' => 'en_US' ), // English<br />
'es' => array( 'name' => "español", 'code' => 'es_ES' ), // Spanish<br />
'et' => array( 'name' => "eesti keel", 'code' => 'et_EE' ), // Estonian <br />
'fi' => array( 'name' => "suomi", 'code' => 'fi_FI' ), // Finnish<br />
'fr' => array( 'name' => "français", 'code' => 'fr_FR' ), // French<br />
'he' => array( 'name' => "עברית", 'code' => 'he_IL' ), // Hebrew <br />
'hi' => array( 'name' => "हिन्दी", 'code' => 'hi_IN' ), // Hindi<br />
'hr' => array( 'name' => "Hrvatski", 'code' => 'hr_HR' ), // Croatian<br />
'hu' => array( 'name' => "magyar", 'code' => 'hu_HU' ), // Hungarian<br />
'id' => array( 'name' => "Bahasa Indonesia", 'code' => 'id_ID' ), // Indonesian<br />
'ms' => array( 'name' => "Bahasa Malaysia", 'code' => 'ms_MY' ), // Malaysian<br />
'it' => array( 'name' => "italiano", 'code' => 'it_IT' ), // Italian<br />
'ja' => array( 'name' => "日本語", 'code' => 'ja_JP' ), // Japanese<br />
'jv' => array( 'name' => "Basa Jawa", 'code' => 'jv_JV' ), // Javanese <br />
'ko' => array( 'name' => "한국어", 'code' => 'ko_KR' ), // Korean<br />
'lt' => array( 'name' => "lietuvių", 'code' => 'lt_LT' ), // Lithuanian<br />
'lv' => array( 'name' => "latviešu", 'code' => 'lv_LV' ), // Latvian<br />
'nl' => array( 'name' => "nederlands", 'code' => 'nl_NL' ), // Dutch<br />
'no' => array( 'name' => "norsk", 'code' => 'nb_NO' ), // Norwegian<br />
'oc' => array( 'name' => "occitan", 'code' => 'oc_FR' ), // Occitan<br />
'pl' => array( 'name' => "polski", 'code' => 'pl_PL' ), // Polish<br />
'pt' => array( 'name' => "português", 'code' => 'pt_PT' ), // Portuguese<br />
'ro' => array( 'name' => "română", 'code' => 'ro_RO' ), // Romanian<br />
'ru' => array( 'name' => "Русский язык", 'code' => 'ru_RU' ), // Russian<br />
'sk' => array( 'name' => "slovenčina", 'code' => 'sk_SK' ), // Slovak<br />
'sl' => array( 'name' => "slovenščina", 'code' => 'sl_SI' ), // Slovenian <br />
'sr' => array( 'name' => "Српски", 'code' => 'sr_RS' ), // Serbian <br />
'sv' => array( 'name' => "svenska", 'code' => 'sv_SE' ), // Swedish<br />
'tr' => array( 'name' => "Türkçe", 'code' => 'tr_TR' ), // Turkish <br />
'uk' => array( 'name' => "Українська мова", 'code' => 'uk_UA' ), // Ukrainian<br />
'zh' => array( 'name' => "中文 (漢)", 'code' => 'zh_TW' ), // Traditional Chinese (Hong Kong, Macau, Taiwan)<br />
'zh-cn' => array( 'name' => "中文 (汉)", 'code' => 'zh_CN' ), // Simplified Chinese (Mainland China, Singapore)<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=8457
Main game logic: yourgamename.game.php
2021-06-06T07:28:57Z
<p>JonChambers: /* Managing errors and exceptions */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* Constructor: where you define global variables.<br />
* setupNewGame: initial setup of the game. Takes an array of players, indexed by player_id. Structure of each player includes player_name, player_canal, player_avatar, and flags indicating admin/ai/premium/order/language/beginner.<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game. Returned value must include ['players'][playerId]['score'] for scores to populate when F5 is pressed.<br />
* getGameProgression: where you compute the game progression indicator. Returns a number indicating percent of progression (0-100)<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions ([https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php more info here]). <br />
* Game state arguments: methods to return additional data on specific game states ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#args more info here]).<br />
* Game state actions: the logic to run when entering a new game state ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#action more info here]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
* upgradeTableDb: function to migrate database if you change it after release on production.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
; isSpectator()<br />
: Check the "current_player" spectator status. If true, the user accessing the game is a spectator (not part of the game). For this user, the interface should display all public information, and no private information (like a friend sitting at the same table as players and just spectating the game).<br />
<br />
; getActivePlayerColor()<br />
: This function does not seems to exist in API, if you need it here is implementation<br />
function getActivePlayerColor() {<br />
$player_id = self::getActivePlayerId();<br />
$players = self::loadPlayersBasicInfos();<br />
if( isset( $players[ $player_id ]) )<br />
return $players[ $player_id ]['player_color'];<br />
else<br />
return null;<br />
}<br />
<br />
== Accessing the database ==<br />
<br />
The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.<br />
<br />
'''IMPORTANT'''<br />
<br />
BGA uses [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. This means that your database changes WON'T BE APPLIED to the database until your request ends normally (web request, not database request). Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed. This also means that you need not (and in fact cannot) use your own transactions for multiple related database operations.<br />
<br />
However there sets of database operation that initite implicit commit (most common mistake is to use "TRUNCATE"), you cannot use these operations during the game, it breaks the unrolling of transactions and will lead to nasty issues<br />
(https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit).<br />
<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE/INSERT query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE/INSERT queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array is an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Same as getCollectionFromDB($sdl), but raise an exception if the collection is empty. Note: this function does NOT have 2nd argument as previous one does.<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Similar to previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: The result is the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of __construct() method of ''yourgamename.game.php''. This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 80 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multipleactiveplayer"<br />
: Note: avoid using this method in a "multipleactiveplayer" state because it does not mean anything.<br />
<br />
=== Multiple activate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (this will trigger '''onUpdateActionButtons''').<br />
: Usually, you use this method at the beginning of a game state (e.g., "stGameState") which transitions to a ''multipleactiveplayer'' state in which multiple players have to perform some action. Do not use this method if you going to make some more changes in the active player list. (I.e., if you want to take away multipleactiveplayer status immediately afterwards, use setPlayersMultiactive instead.)<br />
<br />
Example of usage:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
<br />
And this is the state declaration:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'multiPlayerDoSomething',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "actionBla" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players whose state changed.<br />
: "players" is the array of player id that should be made active.<br />
: If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action. It is also possible to call it directly from multiplayer action handler.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
Example of usage (see state declaration of playerTurnPlace above):<br />
<pre><br />
function actionBla($args) {<br />
self::checkAction('actionBla');<br />
// handle the action using $this->getCurrentPlayerId()<br />
$this->gamestate->setPlayerNonMultiactive( $this->getCurrentPlayerId(), 'next');<br />
}<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; updating database manually<br />
: Use this helper function to change multiactive state without sending notification<br />
<pre><br />
/**<br />
* Changes values of multiactivity in db, does not sent notifications.<br />
* To send notifications after use updateMultiactiveOrNextState<br />
* @param number $player_id, player id <=0 or null - means ALL<br />
* @param number $value - 1 multiactive, 0 non multiactive<br />
*/<br />
function dbSetPlayerMultiactive($player_id = -1, $value = 1) {<br />
if (! $value)<br />
$value = 0;<br />
else<br />
$value = 1;<br />
$sql = "UPDATE player SET player_is_multiactive = '$value' WHERE player_zombie = 0 and player_eliminated = 0";<br />
if ($player_id > 0) {<br />
$sql .= " AND player_id = $player_id";<br />
}<br />
self::DbQuery($sql);<br />
}<br />
</pre><br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter is the name of the transition, and NOT the name of the target game state, see [[Your game state machine: states.inc.php]] for more information about states.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if the current player can perform a specific action in the current game state, and optionally throw an exception if they can't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game. It should not be called from methods where the current player is not necessarily the active player, otherwise it may fail with an "It is not your turn" exception.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
This is how PHP action looks that returns player to active state (only for multiplayeractive states). To be able to execute on js side do not checkAction on js side for this specific one.<br />
<br />
function actionUnpass() {<br />
$this->gamestate->checkPossibleAction('actionUnpass'); // player chane mind about passing while others were thinking<br />
$this->gamestate->setPlayersMultiactive(array ($this->getCurrentPlayerId() ), 'error', false);<br />
}<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table. However there no 0 index here.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
'''createNextPlayerTable( $players, $bLoop=true )'''<br />
<br />
Using $players array creates a map of current => next as in example from getNextPlayerTable(), however you can use custom order here. <br />
Parmeter $bLoop is true if last player points to first, false otherwise.<br />
In any case index 0 points to first player (first element of $players array). $players is array of player ids in desired order.<br />
<br />
N.B: This function '''DOES NOT''' allow to change players turn order!<br />
<br />
Example of usage:<br />
<pre><br />
function getNextPlayerTableCustom() {<br />
$starting = $this->getStartingPlayer(); // custom function to get starting player<br />
$player_ids = $this->getPlayerIdsInOrder($starting); // custom function to create players array starting from starting player<br />
return self::createNextPlayerTable($player_ids, false); // create next player table in custom order<br />
}<br />
</pre><br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translate.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.<br />
<br />
'''Important''': NO private data must be sent with this method, as a cheater could see it even it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
'''Important''': When the game page is reloaded (i.e. F5 or when loading turn based game) all previous notifications are replayed as history notifications. These notifications do not trigger notification handlers and are used basically to build the game log. Because of that most of the notification arguments, expect i18n, player_id and all arguments used in the message, are removed from these history notifications. If you need additional arguments in history notifications you can add special field <b>preserve</b> to notification arguments, like this:<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y,<br />
'preserve' => [ 'x', 'y' ]<br />
) );<br />
</pre><br />
In this example, fields x and y will be preserved when replaying history notification at the game load.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications<br />
* When you read log preview for old games its unreadable (this is log before you enter the game reply, useful for troubleshooting or game analysis)<br />
* Its more data to transfer and store in db<br />
* Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
Special handling of arguments:<br />
* ${player_name} - this will be wrapped in html and text shown using color of the corresponding player, some colors also have reserved background. This will apply recursively as well.<br />
* ${player_name2} - same <br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log. If you want a second player name in the log, name the variable ${player_name2}, etc.<br />
<br />
Note that spectators cannot be notified using this method, because their player ID is not available via loadPlayersBasicInfos() or otherwise. You must use notifyAllPlayers() for any notification that spectators should get.<br />
<br />
== About random and randomness ==<br />
<br />
A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dice and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
This is a BGA framework function that provides you a random number between "min" and "max" (inclusive), using the best available random method available on the system.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
As of now, bga_rand is based on the PHP function "random_int", which ensures a cryptographic level of randomness.<br />
<br />
In particular, it is '''mandatory''' to use it for all '''dice throw''' (ie: games using other methods for dice throwing will be rejected by BGA during review).<br />
<br />
Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
To shuffle items, like a pile of cards, the best way is to use the BGA PHP [[Deck]] component and to use "shuffle" method. This ensures that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.<br />
<br />
As of now, the Deck component shuffle method is based on PHP "shuffle" method, which has quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as they are based on PHP "shuffle" function (or similar, like "array_rand").<br />
<br />
=== Other methods ===<br />
<br />
Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistic is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you define statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
<br />
This method must be called for each statistic of your game, in your setupNewGame method.<br />
If you neglect to call this for a statistic, and also do not update the value during the course of a certain game using setStat or incStat, the value of the stat will be undefined rather than 0. This will result in it being ignored at the end of the game, as if it didn't apply to that particular game, and excluded from cumulative statistics. As a consequence - if do not want statistic to be applied, do not init it, or call set or inc on it.<br />
<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistic, or "player" if this is a player statistic.<br />
<br />
'$name' is the name of your statistic, as it has been defined in your stats.inc.php file.<br />
<br />
'$value' is the initial value of the statistic. If this is a player statistic and if the player is not specified by "$player_id" argument, the value is set for ALL players.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone win/lose together in a full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
Assign a score of zero to everyone if it's a loss.<br />
Assign the same score > 0 to everyone if it's a win.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone loses = everyone is tied. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.inc.php:<br />
<br />
<pre><br />
// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative or zero score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.<br />
<br />
This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* the player is informed in a dialog box that he no longer have to play and can start another game if he/she wants too (with buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.<br />
* When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
These functions should have been API but they are not, just add them to your php game and use for every game.<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see [[BGA Undo policy]]). Cannot use in multiactivate state or in game state where next state is multiactive.<br />
: Note: this function does not actually do anything when it is called, it only raises the flag to store the database AFTER transaction is over. So the actual state will be saved when you exit the function calling it (technically before first queued notification is sent, which matter if you transition to game state not to user state after), which may affect what you end up saved.<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
function actionUndo() {<br />
self::checkAction('actionUndo');<br />
$this->undoRestorePoint();<br />
$this->gamestate->nextState('next'); // transition to single player state (i.e. beginning of player actions for this turn)<br />
}<br />
<br />
== Managing errors and exceptions ==<br />
<br />
Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that they are not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: Base class to notify a system exception. The message will be hidden from the user, but show in the logs. Use this if the message contains technical information.<br />
: You shouldn't use this type of exception except if you think the information shown could be critical. Indeed: a generic error message will be shown to the user, so it's going to be difficult for you to see what happened.<br />
<br />
== Zombie mode ==<br />
<br />
When a player leaves a game for any reason (expelled, quit), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but this is cool if the other players can finish the game anyway. That's why zombie mode exists: allow the other player to finish the game, even if the situation is not ideal.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
* Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it. Trying to end the game is not supported by the framework and will likely cause unexpected errors.<br />
<br />
Most of the time, the best thing to do when it is zombie player turn is to jump immediately to a state where he is not active anymore. For example, if he is in a game state where he has a choice between playing A and playing B, the best thing to do is NOT to choose A or B, but to pass. So, even if there's no "pass" action in the rules, add a "zombiepass" transitition in your game state and use it.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
// NB: this parameter is used only to flag games supporting this feature; you must use (or not use) reattributeColorsBasedOnPreferences PHP method to actually enable or disable the feature.<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.<br />
* your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Legacy games API ==<br />
<br />
For some very specific games ("legacy", "campaign"), you need to keep some informations from a game to another.<br />
<br />
This should be an exceptional situation: the legacy API is costing resources on Board Game Arena databases, and is slowing down the game setup process + game end of game process. Please do not use it for things like:<br />
* keeping a player preference/settings (=> player preferences and game options should be used instead)<br />
* keeping a statistics, a score, or a ranking, while it is not planned in the physical board game, or while there is no added value compared to BGA statistics / rankings.<br />
<br />
You should use it for:<br />
* legacy games: when some components of the game has been altered in a previous game and should be kept as it is.<br />
* "campaign style" games: when a player is getting a "reward" at the end of a game, and should be able to use it in further games.<br />
<br />
Important: you cannot store more than 64k of data (serialized as JSON) per player per game. If you go over 64k, storeLegacyData function is going to FAIL, and there is a risk to create a major bug (= players blocked) in your game. You MUST make sure that no more than 64k of data is used for each player for your game. For example, if you are implementing a "campaign style" game and if you allow a player to start multiple campaign, you must LIMIT the number of different campaign so that the total data size to not go over the limit. We strongly recommend you to use this:<br />
<br />
try <br />
{<br />
$this->storeLegacyTeamData( 'my_variable', $my_data );<br />
}<br />
catch( feException $e )<br />
{<br />
if( $e->getCode() == FEX_legacy_size_exceeded )<br />
{<br />
// Do something here to free some space in Legacy data (ex: by removing some variables)<br />
}<br />
else<br />
throw $e;<br />
}<br />
<br />
The keys may only contain letters and numbers, underscore seems not to be allowed.<br />
<br />
; function storeLegacyData( $player_id, $key, $data, $ttl = 365 )<br />
: Store some data associated with $key for the given user / current game<br />
: In the opposite of all other game data, this data will PERSIST after the end of this table, and can be re-used<br />
: in a future table with the same game.<br />
: IMPORTANT: The only possible place where you can use this method is when the game is over at your table (last game action). Otherwise, there is a risk of conflicts between ongoing games. <br />
: TTL is a time-to-live: the maximum, and default, is 365 days.<br />
: In any way, the total data (= all keys) you can store for a given user+game is 64k (note: data is store serialized as JSON data)<br />
<br />
; function retrieveLegacyData( $player_id, $key )<br />
: Get data associated with $key for the current game<br />
: This data is common to ALL tables from the same game for this player, and persist from one table to another.<br />
: Note: calling this function has an important cost => please call it few times (possibly: only ONCE) for each player for 1 game if possible<br />
: Note: you can use '%' in $key to retrieve all keys matching the given patterns<br />
<br />
; function removeLegacyData( $player_id, $key )<br />
: Remove some legacy data with the given key<br />
: (useful to free some data to avoid going over 64k)<br />
<br />
<br />
<br />
; function storeLegacyTeamData( $key, $data, $ttl = 365 )<br />
: Same as storeLegacyData, except that it stores some data for the whole team within the current table<br />
: Ie: if players A, B and C are at a table, the legacy data will be saved for future table with (exactly) A, B and C on the table.<br />
: This is useful for games which are intended to be played several time by the same team.<br />
: Note: the data total size is still limited, so you must implement catch the FEX_legacy_size_exceeded exception if it happens<br />
<br />
; function retrieveLegacyTeamData($key)<br />
: Same as retrieveLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
; function removeLegacyTeamData()<br />
: Same as removeLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
== Language dependent games API ==<br />
<br />
This API is used for games that are heavily language dependent. Two most common use cases are:<br />
* Games that have a language dependent component that are not necessarily translatable, typically a list of words. (Think of games like Codenames, Decrypto, Just One...)<br />
* Games with massive communication where players would like to ensure that all participants speak the same language. (Think of games like Werewolf, The Resistance, maybe even dixit...)<br />
<br />
If this option is used, the table created will be limited only to users that have specific language in their profile. Player starting the game would be able to chose one of the languages he speaks.<br />
<br />
There is a new property language_dependency in gameinfos.inc.php which can be set like this:<br />
'language_dependecy' => false, //or if the property is missing, the game is not language dependent<br />
'language_dependecy' => true, //all players at the table must speak the same language<br />
'language_dependecy' => array( 1 => 'en', 2 => 'fr', 3 => 'it' ), //1-based list of supported languages<br />
<br />
In the gamename.game.php file, you can get the id of selected language with the method '''getGameLanguage'''.<br />
; function getGameLanguage()<br />
: Returns an index of the selected language as defined in gameinfos.inc.php.<br />
<br />
Languages currently available on BGA are:<br />
'ar' => array( 'name' => "العربية", 'code' => 'ar_AE' ), // Arabic<br />
'be' => array( 'name' => "беларуская мова", 'code' => 'be_BY' ), // Belarusian<br />
'bn' => array( 'name' => "বাংলা", 'code' => 'bn_BD' ), // Bengali<br />
'bg' => array( 'name' => "български език", 'code' => 'bg_BG' ), // Bulgarian<br />
'ca' => array( 'name' => "català", 'code' => 'ca_ES' ), // Catalan<br />
'cs' => array( 'name' => "čeština", 'code' => 'cs_CZ' ), // Czech<br />
'da' => array( 'name' => "dansk", 'code' => 'da_DK' ), // Danish<br />
'de' => array( 'name' => "deutsch", 'code' => 'de_DE' ), // German<br />
'el' => array( 'name' => "Ελληνικά", 'code' => 'el_GR' ), // Greek<br />
'en' => array( 'name' => "English", 'code' => 'en_US' ), // English<br />
'es' => array( 'name' => "español", 'code' => 'es_ES' ), // Spanish<br />
'et' => array( 'name' => "eesti keel", 'code' => 'et_EE' ), // Estonian <br />
'fi' => array( 'name' => "suomi", 'code' => 'fi_FI' ), // Finnish<br />
'fr' => array( 'name' => "français", 'code' => 'fr_FR' ), // French<br />
'he' => array( 'name' => "עברית", 'code' => 'he_IL' ), // Hebrew <br />
'hi' => array( 'name' => "हिन्दी", 'code' => 'hi_IN' ), // Hindi<br />
'hr' => array( 'name' => "Hrvatski", 'code' => 'hr_HR' ), // Croatian<br />
'hu' => array( 'name' => "magyar", 'code' => 'hu_HU' ), // Hungarian<br />
'id' => array( 'name' => "Bahasa Indonesia", 'code' => 'id_ID' ), // Indonesian<br />
'ms' => array( 'name' => "Bahasa Malaysia", 'code' => 'ms_MY' ), // Malaysian<br />
'it' => array( 'name' => "italiano", 'code' => 'it_IT' ), // Italian<br />
'ja' => array( 'name' => "日本語", 'code' => 'ja_JP' ), // Japanese<br />
'jv' => array( 'name' => "Basa Jawa", 'code' => 'jv_JV' ), // Javanese <br />
'ko' => array( 'name' => "한국어", 'code' => 'ko_KR' ), // Korean<br />
'lt' => array( 'name' => "lietuvių", 'code' => 'lt_LT' ), // Lithuanian<br />
'lv' => array( 'name' => "latviešu", 'code' => 'lv_LV' ), // Latvian<br />
'nl' => array( 'name' => "nederlands", 'code' => 'nl_NL' ), // Dutch<br />
'no' => array( 'name' => "norsk", 'code' => 'nb_NO' ), // Norwegian<br />
'oc' => array( 'name' => "occitan", 'code' => 'oc_FR' ), // Occitan<br />
'pl' => array( 'name' => "polski", 'code' => 'pl_PL' ), // Polish<br />
'pt' => array( 'name' => "português", 'code' => 'pt_PT' ), // Portuguese<br />
'ro' => array( 'name' => "română", 'code' => 'ro_RO' ), // Romanian<br />
'ru' => array( 'name' => "Русский язык", 'code' => 'ru_RU' ), // Russian<br />
'sk' => array( 'name' => "slovenčina", 'code' => 'sk_SK' ), // Slovak<br />
'sl' => array( 'name' => "slovenščina", 'code' => 'sl_SI' ), // Slovenian <br />
'sr' => array( 'name' => "Српски", 'code' => 'sr_RS' ), // Serbian <br />
'sv' => array( 'name' => "svenska", 'code' => 'sv_SE' ), // Swedish<br />
'tr' => array( 'name' => "Türkçe", 'code' => 'tr_TR' ), // Turkish <br />
'uk' => array( 'name' => "Українська мова", 'code' => 'uk_UA' ), // Ukrainian<br />
'zh' => array( 'name' => "中文 (漢)", 'code' => 'zh_TW' ), // Traditional Chinese (Hong Kong, Macau, Taiwan)<br />
'zh-cn' => array( 'name' => "中文 (汉)", 'code' => 'zh_CN' ), // Simplified Chinese (Mainland China, Singapore)<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Tutorial_reversi&diff=8456
Tutorial reversi
2021-06-06T06:49:51Z
<p>JonChambers: /* Let's play */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Reversi.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the [http://en.wikipedia.org/wiki/Reversi#Rules rules of Reversi].<br />
* Know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
<br />
== Create your first game ==<br />
<br />
With the initial skeleton of code provided initially, you can already start a game from the BGA Studio. For now, we are going to work with one player only. Most of the time it is simpler to proceed with only one player during the early phase of development of your game, as it's easy and fast to start/stop games.<br />
<br />
(If you choose to start with 2 players, you should see two names on the right: testdude0 and testdude1. To switch between them, press the red arrow button near their names; it will open another tab. This way you don't need to login and logout from multiple accounts.) <br />
<br />
Reminder: Always use the "Express Start" button to start the game. <br />
<br />
Thus, you can start a "Reversi" game, and arrive on a void, empty game. Yeah.<br />
<br />
== Let it look like Reversi ==<br />
<br />
It's always a good idea to start with a little bit of graphic work. Why? Because this helps to figure out how the final game will be, and issues that may appear later.<br />
<br />
Be careful designing the layout of your game: you must always keep in mind that players with a 1024px screen width must be able to play. Usually, it means that the width of the play area can be 750px (in the worst case).<br />
<br />
For Reversi, it's useless to have a 750x750px board - much too big, so we choose this one which fit perfectly (536x528):<br />
<br />
[[File:Board.jpg]]<br />
<br />
Note that we are using a jpg file. Jpg are lighter than png, so faster to load. Later we are going to use PNGs for discs for transparency purpose.<br />
<br />
Now, let's make it appear in our game:<br />
* upload board.jpg in your "img/" directory.<br />
* edit "reversi_reversi.tpl" to add a 'div' for your board.<br />
<br />
Note: If you are building this game by following the tutorial, you will have a different project name than 'reversi'. The file names in your project will be different than shown in this tutorial, replacing 'reversi' with your project name. It should be trivial to find the right file in your project, but be sure that any code (other than comments) that references 'reversi' is changed to your actual project name.<br />
<br />
<pre><br />
<div id="board"><br />
</div><br />
</pre><br />
<br />
* edit your reversi.css file to transform it into a visible board:<br />
<br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
}<br />
<br />
Refresh your page. Here's your board:<br />
<br />
[[File:reversi1.jpg]]<br />
<br />
== Make the squares appear ==<br />
<br />
Now, we need to create some invisible HTML elements where squares are. These elements will be used as position references for white and black discs. <br />
We need 64 squares. To avoid writing 64 'div' elements on our template, we are going to use the "block" feature.<br />
<br />
Let's modify our template like this:<br />
<br />
<pre><br />
<div id="board"><br />
<!-- BEGIN square --><br />
<div id="square_{X}_{Y}" class="square" style="left: {LEFT}px; top: {TOP}px;"></div><br />
<!-- END square --><br />
</div><br />
</pre><br />
<br />
As you can see, we created a "square" block, with 4 variable elements: X, Y, LEFT and TOP. We are going to use this block 64 times during page load.<br />
<br />
Let's do it in our "reversi.view.php" file:<br />
<br />
<pre><br />
$this->page->begin_block( "reversi_reversi", "square" );<br />
<br />
$hor_scale = 64.8;<br />
$ver_scale = 64.4;<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$this->page->insert_block( "square", array(<br />
'X' => $x,<br />
'Y' => $y,<br />
'LEFT' => round( ($x-1)*$hor_scale+10 ),<br />
'TOP' => round( ($y-1)*$ver_scale+7 )<br />
) );<br />
} <br />
}<br />
</pre><br />
<br />
Note: as you can see, squares in our "board.jpg" files do not have an exact width/height in pixels, and that's the reason we are using floating point numbers here.<br />
<br />
Now, to finish our work and check if everything works fine, we are going to style our square a little bit in our CSS stylesheet:<br />
<br />
<pre><br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
position: relative;<br />
}<br />
<br />
.square {<br />
width: 62px;<br />
height: 62px;<br />
position: absolute;<br />
background-color: red;<br />
}<br />
</pre><br />
<br />
Explanations:<br />
* With "position: relative" on board, we ensure square elements are positioned relatively to board.<br />
* For the test, we use a red background color for the square. This is a useful tip to figure out if everything is fine with invisible elements.<br />
<br />
Let's refresh and check our our (beautiful) squares:<br />
<br />
[[File:reversi2.jpg]]<br />
<br />
Hint: Now that we know our squares are set up correctly, we can hide the red background. You can remove the "background-color: red;" line from your .square class in the CSS stylesheet.<br />
<br />
== The discs ==<br />
<br />
Now, our board is ready to receive some disc tokens!<br />
<br />
[Note: Throughout this tutorial, sometimes "tokens" is used, and sometimes "discs" is used. They are often swapped if you're looking at code in the reversi example project.]<br />
<br />
At first, we introduce a new 'div' element as a child of "board" to host all these tokens (in our template):<br />
<br />
<pre><br />
<!-- END square --><br />
<br />
<div id="tokens"><br />
</div><br />
</div><br />
</pre><br />
<br />
Then, let's introduce a new piece of art with the discs. We need some transparency here so we are using a png file:<br />
<br />
[[File:tokens.png]]<br />
<br />
Upload this image file "tokens.png" in your "img/" directory.<br />
<br />
Important: we are using ONE file for both discs. It's really important that you use a minimum number of graphic files for your game with this "CSS sprite" technique, because it makes the game loading faster and more reliable. [http://www.w3schools.com/css/css_image_sprites.asp Read more about CSS sprites].<br />
<br />
Now, let's separate the disc with some CSS stuff:<br />
<br />
<pre><br />
.token {<br />
width: 56px;<br />
height: 56px;<br />
position: absolute;<br />
background-image: url('img/tokens.png');<br />
}<br />
.tokencolor_ffffff { background-position: 0px 0px; }<br />
.tokencolor_000000 { background-position: -56px 0px; }<br />
</pre><br />
<br />
With this CSS code, we apply the classes "token" and "tokencolor_ffffff" to a div element and we've got a white token. Yeah.<br />
<br />
Note the "position: absolute" which allows us to position tokens on the board and make them "slide" to their positions.<br />
<br />
Now, let's make a first token appear on our board. Disc tokens are not visible at the beginning of the game: they appear dynamically during the game. For this reason, we are going to make them appear from our Javascript code, with a BGA Framework technique called "JS template".<br />
<br />
In our template file (reversi_reversi.tpl), let's create the piece of HTML needed to display our token:<br />
<br />
<pre><br />
<script type="text/javascript"><br />
<br />
// Templates<br />
<br />
var jstpl_token='<div class="token tokencolor_${color}" id="token_${x_y}"></div>';<br />
<br />
</script><br />
</pre><br />
<br />
Note: we already created the "templates" section for you in the game skeleton.<br />
<br />
As you can see, we defined a JS template named "jstpl_token" with a piece of HTML and two variables: the color of the token and its x/y coordinates. Note that the syntax of the argument is different for template block variables (braces <code>{}</code>) and JS template variables (dollar and braces <code>${}</code>).<br />
<br />
Now, let's create a method in our Javascript code (in the "reversi.js" file) that will make a token appear on the board, using this template. Add under the section //// Utility methods:<br />
<br />
<pre><br />
addTokenOnBoard: function( x, y, player )<br />
{<br />
dojo.place( this.format_block( 'jstpl_token', {<br />
x_y: x+'_'+y,<br />
color: this.gamedatas.players[ player ].color<br />
} ) , 'tokens' );<br />
<br />
this.placeOnObject( 'token_'+x+'_'+y, 'overall_player_board_'+player );<br />
this.slideToObject( 'token_'+x+'_'+y, 'square_'+x+'_'+y ).play();<br />
},<br />
</pre><br />
<br />
At first, with "dojo.place" and "this.format_block" methods, we create a HTML piece of code and insert it as a new child of "tokens" div element.<br />
<br />
Then, with BGA "this.placeOnObject" method, we place this element over the panel of some player. Immediately after, using BGA "this.slideToObject" method, we make the disc slide to the "square" element, its final destination.<br />
<br />
"'overall_player_board_'+player" refers to the div element that contains each player's information and avatar. By initially placing the token here, it gives the effect that the player's avatar is throwing the token onto the board.<br />
<br />
Note: don't forget to call the "play()", otherwise the token remains at its original location.<br />
<br />
Note: note that during all the process, the parent of the new disc HTML element will remain "tokens". placeOnObject and slideToObject methods are only moving the position of elements on screen, and they are not modifying the HTML tree.<br />
<br />
Before we can show a token, we need to set the player colors in the setupNewGame function in reversi.game.php:<br />
<pre><br />
$default_colors = array( "ffffff", "000000" );<br />
</pre><br />
<br />
Note: Probably you'll have to remove the line "self::reattributeColorsBasedOnPreferences( $players, $gameinfos['player_colors'] );".<br />
<br />
<br />
Now, to test if everything works fine, just add "this.addTokenOnBoard( 2, 2, [player_id] )" in the "setup" Javascript method in reversi.js, and reload the page.<br />
<br />
<br />
A token should appear and slide immediately to its position, like this:<br />
<br />
[[File:reversi3.jpg]]<br />
<br />
== The database ==<br />
<br />
We did most of the client-side programming, so let's have a look on the other side now.<br />
<br />
To design the database model of our game, the best thing to do is to follow the "Go to game database" link at the bottom of our game, to access the database directly with a [http://www.phpmyadmin.net/ PhpMyAdmin] instance. Your PhpMyAdmin username/password is in your welcome email (and currently the same as the SFTP username/password).<br />
<br />
Then, you can create the tables you need for your table (do not remove existing tables!), and report every SQL command used in your "dbmodel.sql" file. How do you generate SQL to create table after creating the table in the UI? See [https://www.itsupportguides.com/knowledge-base/tech-tips-tricks/how-to-generate-sql-create-table-script-using-phpmyadmin/ here]. Add 'IF NOT EXISTS' to the CREATE TABLE sql (see example below).<br />
<br />
[[File:reversi4.jpg]]<br />
<br />
The database model of Reversi is very simple: just one table with the squares of the board. <br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `board` (<br />
`board_x` smallint(5) unsigned NOT NULL,<br />
`board_y` smallint(5) unsigned NOT NULL,<br />
`board_player` int(10) unsigned DEFAULT NULL,<br />
PRIMARY KEY (`board_x`,`board_y`)<br />
) ENGINE=InnoDB;<br />
</pre><br />
<br />
Add the above SQL to dbmodel.sql. Now, a new database with a "board" table will be created each time we start a Reversi game. This is why after modifying our dbmodel.sql it's a good time to stop & start again our game.<br />
<br />
== Setup the initial game position ==<br />
<br />
The "setupNewGame" method of our reversi.game.php is called during initial setup: this is the place to initialize our data and to place the initial tokens on the board (initially, there are 4 tokens on the board).<br />
<br />
Let's do this:<br />
<br />
<pre><br />
// Init the board<br />
$sql = "INSERT INTO board (board_x,board_y,board_player) VALUES ";<br />
$sql_values = array();<br />
list( $blackplayer_id, $whiteplayer_id ) = array_keys( $players );<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$token_value = "NULL";<br />
if( ($x==4 && $y==4) || ($x==5 && $y==5) ) // Initial positions of white player<br />
$token_value = "'$whiteplayer_id'";<br />
else if( ($x==4 && $y==5) || ($x==5 && $y==4) ) // Initial positions of black player<br />
$token_value = "'$blackplayer_id'";<br />
<br />
$sql_values[] = "('$x','$y',$token_value)";<br />
}<br />
}<br />
$sql .= implode( ',', $sql_values );<br />
self::DbQuery( $sql );<br />
<br />
<br />
// Active first player<br />
self::activeNextPlayer(); <br />
</pre><br />
<br />
We create one table entry for each square, with a "NULL" value which means "empty square". For 4 specific squares, we place an initial token.<br />
<br />
At the end, we call activeNextPlayer to make the first player active at the beginning of the game.<br />
<br />
You need to remove the call to self::reattributeColorsBasedOnPreferences() in SetupNewGame() so that any player color preferences do not override the two colors supported here.<br />
<br />
Now, we need to make these tokens appear on the client side. The first step is to return the token positions with our "getAllDatas" PHP method (called during each page reload):<br />
<br />
<pre><br />
// Get reversi board token<br />
$result['board'] = self::getObjectListFromDB( "SELECT board_x x, board_y y, board_player player<br />
FROM board<br />
WHERE board_player IS NOT NULL" );<br />
</pre><br />
<br />
We are using the BGA framework "getObjectListFromDB" method that formats the result of this SQL query in a PHP array with x, y and player attributes.<br />
<br />
Last, we process this array client side, and place a disc token on the board for each array item. We do this using our Javascript "setup" method:<br />
<br />
<pre><br />
for( var i in gamedatas.board )<br />
{<br />
var square = gamedatas.board[i];<br />
<br />
if( square.player !== null )<br />
{<br />
this.addTokenOnBoard( square.x, square.y, square.player );<br />
}<br />
}<br />
</pre><br />
<br />
Our "board" entry created in "getAllDatas" can be used here as "gamedatas.board" in our Javascript. We are using our previously developed "addTokenOnBoard" method.<br />
<br />
Reload... and here we are:<br />
<br />
[[File:reversi5.jpg]]<br />
<br />
It starts to smell Reversi here...<br />
<br />
== The game state machine ==<br />
<br />
Now, let's stop our game again, because we are going to start the core game logic.<br />
<br />
You already read [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine], so you know that this is the heart of your game logic. For reversi, it's very simple. Here's a diagram of our game state machine:<br />
<br />
[[File:reversi6.jpg]]<br />
<br />
And here's our "states.inc.php", according to this diagram:<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 10 )<br />
),<br />
<br />
10 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a disc'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a disc'),<br />
"type" => "activeplayer",<br />
"args" => "argPlayerTurn",<br />
"possibleactions" => array( 'playDisc' ),<br />
"transitions" => array( "playDisc" => 11, "zombiePass" => 11 )<br />
),<br />
<br />
11 => array(<br />
"name" => "nextPlayer",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "nextTurn" => 10, "cantPlay" => 11, "endGame" => 99 )<br />
),<br />
<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
Now, let's create in reversi.game.php the methods that are declared in this game states description file:<br />
* argPlayerTurn<br />
* stNextPlayer<br />
<br />
... and start a new Reversi game.<br />
<br />
As you can see on the screen capture below, the BGA framework makes the game jump to our first game state "playerTurn" right after the initial setup. That's why the status bar contains the description of playerTurn state ("XXXX must play a disc"):<br />
<br />
[[File:reversi7.jpg]]<br />
<br />
== The rules ==<br />
<br />
We will use the "getPossibleMoves" PHP method to:<br />
* Indicate to the current player where she is allowed to play by returning a list of coordinates<br />
* Check if the player has the right to play in the spot they choose<br />
<br />
<br />
This is pure PHP programming here, and there are no special things from the BGA framework that can be used. This is why we won't go into details here. The overall idea is:<br />
* Create a "getTurnedOverDiscs(x,y)" method that returns coordinates of discs that would be turned over if a token would be played at x,y.<br />
* Loop through all free squares of the board and call the "getTurnedOverDiscs" method on each of them. If at least 1 token is turned over, this is a valid move.<br />
<br />
IMPORTANT: Keep in mind that making a database query is slow, so please don't load the entire game board with a SQL query multiple times. In our implementation, we load the entire board once at the beginning of "getPossibleMoves", and then pass the board as an argument to all methods.<br />
<br />
If you want to look into details, please look at the "utility method" sections of reversi.game.php. If building the tutorial yourself, copy the functions under "Utility functions" comment from the Reversi tutorial.<br />
<br />
== Display allowed moves ==<br />
<br />
Now we want to highlight the squares where the player can place a disc.<br />
<br />
To do this, we add a "argPlayerTurn" method in reversi.game.php. This method is called on the server each time we enter into "playerTurn" game state, and its result is transferred automatically to the client-side:<br />
<br />
<pre><br />
function argPlayerTurn()<br />
{<br />
return array(<br />
'possibleMoves' => self::getPossibleMoves( self::getActivePlayerId() )<br />
);<br />
}<br />
</pre><br />
<br />
We use the "getPossibleMoves" method we just developed.<br />
<br />
Each time we enter into a new game state, we use the "onEnteringState" Javascript method (in the reversi.js file, under "Game & client states"). This lets us use the data returned by the method above on the client side.<br />
<br />
<pre><br />
onEnteringState: function( stateName, args )<br />
{<br />
console.log( 'Entering state: '+stateName );<br />
<br />
switch( stateName )<br />
{<br />
case 'playerTurn':<br />
this.updatePossibleMoves( args.args.possibleMoves );<br />
break;<br />
}<br />
},<br />
</pre><br />
<br />
So, when we enter into "playerTurn" game state, we call our "updatePossibleMoves" method (under the "Utility functions" section). This method looks like this:<br />
<br />
<pre><br />
updatePossibleMoves: function( possibleMoves )<br />
{<br />
// Remove current possible moves<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
<br />
for( var x in possibleMoves )<br />
{<br />
for( var y in possibleMoves[ x ] )<br />
{<br />
// x,y is a possible move<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
} <br />
}<br />
<br />
this.addTooltipToClass( 'possibleMove', '', _('Place a disc here') );<br />
},<br />
</pre><br />
Here's what this does. At first, it removes all "possibleMove" classes currently applied with the very useful combination of "dojo.query" and "removeClass" method.<br />
<br />
Then it loops through all possible moves our PHP "updatePossibleMoves" function created for us, and adds the "possibleMove" class to each corresponding square.<br />
<br />
Finally, it uses the BGA framework "addTooltipToClass" method to associate a tooltip to all those highlighted squares so that players can understand their meaning.<br />
<br />
To see the possible moves we need to create a CSS class ("possibleMove") that can be applied to a "square" element to highlight it:<br />
<pre><br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
</pre><br />
<br />
<br />
<br />
And here we are:<br />
<br />
[[File:reversi8.jpg.jpg]]<br />
<br />
== Let's play ==<br />
<br />
From now, it's better to restart a game with 2 players, because we are going to implement a complete Reversi turn. The summary of what we are going to do is:<br />
* When we click on a "possibleMove" square, send the move to the server.<br />
* Server side, check the move is correct, apply Reversi rules and jump to next player.<br />
* Client side, change the disc position to reflect the move.<br />
<br />
First we associate each click on a square to one of our methods using our Javascript "setup" method:<br />
<br />
<pre><br />
dojo.query( '.square' ).connect( 'onclick', this, 'onPlayDisc' );<br />
</pre><br />
<br />
Note the use of the "dojo.query" method to get all HTML elements with "square" class in just one function call. Now, our "onPlayDisc" method is called each time someone clicks on a square.<br />
<br />
Here's our "onPlayDisc" method below:<br />
<br />
<pre><br />
onPlayDisc: function( evt )<br />
{<br />
// Stop this event propagation<br />
dojo.stopEvent( evt );<br />
<br />
// Get the clicked square x and y<br />
// Note: square id format is "square_X_Y"<br />
var coords = evt.currentTarget.id.split('_');<br />
var x = coords[1];<br />
var y = coords[2];<br />
<br />
if( ! dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{<br />
// This is not a possible move => the click does nothing<br />
return ;<br />
}<br />
<br />
if( this.checkAction( 'playDisc' ) ) // Check that this action is possible at this moment<br />
{ <br />
this.ajaxcall( "/reversi/reversi/playDisc.html", {<br />
x:x,<br />
y:y<br />
}, this, function( result ) {} );<br />
} <br />
},<br />
</pre><br />
<br />
What we do here is:<br />
* We stop the propagation of the Javascript "onclick" event. Otherwise, it can lead to random behavior so it's always a good idea.<br />
* We get the x/y coordinates of the square by using "evt.currentTarget.id".<br />
* We check that clicked square has the "possibleMove" class, otherwise we know for sure that we can't play there.<br />
* We check that "playDisc" action is possible, according to current game state (see "possibleactions" entry in our "playerTurn" game state defined above). This check is important to avoid issues if a player double clicks on a square.<br />
* Finally, we make a call to the server using BGA "ajaxcall" method with argument x and y. Be sure to update the first parameter to match your game if building the tutorial yourself. E.g. "/yourgamename/yourgamename/playDisc.html"<br />
<br />
Now, we have to manage this "playDisc" action on the server side. At first, we introduce a "playDisc" entry point in our "reversi.action.php":<br />
<br />
<pre><br />
public function playDisc()<br />
{<br />
self::setAjaxMode(); <br />
$x = self::getArg( "x", AT_posint, true );<br />
$y = self::getArg( "y", AT_posint, true );<br />
$result = $this->game->playDisc( $x, $y );<br />
self::ajaxResponse( );<br />
}<br />
</pre><br />
<br />
As you can see, we get the 2 arguments x and y from the javascript call, and call a corresponding "playDisc" method in our game logic (reversi.game.php).<br />
<br />
Now, let's have a look of this playDisc method:<br />
<br />
<pre><br />
function playDisc( $x, $y )<br />
{<br />
// Check that this player is active and that this action is possible at this moment<br />
self::checkAction( 'playDisc' ); <br />
</pre><br />
<br />
... at first, we check that this action is possible according to current game state (see "possible action"). We already did it on client side, but it's important to do it on server side too (otherwise it would be possible to cheat).<br />
<br />
<pre><br />
// Now, check if this is a possible move<br />
$board = self::getBoard();<br />
$player_id = self::getActivePlayerId();<br />
$turnedOverDiscs = self::getTurnedOverDiscs( $x, $y, $player_id, $board );<br />
<br />
if( count( $turnedOverDiscs ) > 0 )<br />
{<br />
// This move is possible!<br />
</pre><br />
<br />
...now, we are using the "getTurnedOverDiscs" method again to check that this move is possible.<br />
<br />
<pre><br />
// Let's place a disc at x,y and return all "$returned" discs to the active player<br />
<br />
$sql = "UPDATE board SET board_player='$player_id'<br />
WHERE ( board_x, board_y) IN ( ";<br />
<br />
foreach( $turnedOverDiscs as $turnedOver )<br />
{<br />
$sql .= "('".$turnedOver['x']."','".$turnedOver['y']."'),";<br />
}<br />
$sql .= "('$x','$y') ) ";<br />
<br />
self::DbQuery( $sql );<br />
<br />
</pre><br />
<br />
... we update the database to change the color of all turned over disc + the disc we just placed.<br />
<br />
<pre><br />
// Update scores according to the number of disc on board<br />
$sql = "UPDATE player<br />
SET player_score = (<br />
SELECT COUNT( board_x ) FROM board WHERE board_player=player_id<br />
)";<br />
self::DbQuery( $sql );<br />
<br />
// Statistics<br />
self::incStat( count( $turnedOverDiscs ), "turnedOver", $player_id );<br />
if( ($x==1 && $y==1) || ($x==8 && $y==1) || ($x==1 && $y==8) || ($x==8 && $y==8) )<br />
self::incStat( 1, 'discPlayedOnCorner', $player_id );<br />
else if( $x==1 || $x==8 || $y==1 || $y==8 )<br />
self::incStat( 1, 'discPlayedOnBorder', $player_id );<br />
else if( $x>=3 && $x<=6 && $y>=3 && $y<=6 )<br />
self::incStat( 1, 'discPlayedOnCenter', $player_id );<br />
<br />
</pre><br />
<br />
... now, we update both player score by counting all disc, and we manage game statistics.<br />
<br />
<pre><br />
// Notify<br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ), array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
<br />
self::notifyAllPlayers( "turnOverDiscs", '', array(<br />
'player_id' => $player_id,<br />
'turnedOver' => $turnedOverDiscs<br />
) );<br />
<br />
$newScores = self::getCollectionFromDb( "SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", "", array(<br />
"scores" => $newScores<br />
) );<br />
</pre><br />
<br />
... then we notify about all these changes. We are using for that 3 notifications ('playDisc', 'turnOverDiscs' and 'newScores' that we are going to implement on client side later). Note that the description of the 'playDisc' notification will be logged in the game log.<br />
<br />
<pre><br />
// Then, go to the next state<br />
$this->gamestate->nextState( 'playDisc' );<br />
}<br />
else<br />
throw new feException( "Impossible move" );<br />
}<br />
</pre><br />
<br />
... finally, we jump to the next game state if everything goes fine ('playDisc' is the name of a transition in the 'playerTurn' game state description above which leads to state 11 which is 'nextPlayer').<br />
<br />
To make the statistics work, we have to initialize them in stats.inc.php:<br />
<br />
<pre><br />
// Statistics existing for each player<br />
"player" => array(<br />
<br />
"discPlayedOnCorner" => array( "id"=> 10,<br />
"name" => totranslate("Discs played on a corner"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnBorder" => array( "id"=> 11,<br />
"name" => totranslate("Discs played on a border"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnCenter" => array( "id"=> 12,<br />
"name" => totranslate("Discs played on board center part"), <br />
"type" => "int" ),<br />
<br />
"turnedOver" => array( "id"=> 13,<br />
"name" => totranslate("Number of discs turned over"), <br />
"type" => "int" ) <br />
)<br />
</pre><br />
<br />
A last thing to do on the server side is to activate the next player when we enter the "nextPlayer" game state (in the "reversi.game.php" file, under "Game state reactions"):<br />
<br />
<pre><br />
function stNextPlayer()<br />
{<br />
// Active next player<br />
$player_id = self::activeNextPlayer();<br />
<br />
// Check if both player has at least 1 discs, and if there are free squares to play<br />
$player_to_discs = self::getCollectionFromDb( "SELECT board_player, COUNT( board_x )<br />
FROM board<br />
GROUP BY board_player", true );<br />
<br />
if( ! isset( $player_to_discs[ null ] ) )<br />
{<br />
// Index 0 has not been set => there's no more free place on the board !<br />
// => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
else if( ! isset( $player_to_discs[ $player_id ] ) )<br />
{<br />
// Active player has no more disc on the board => he looses immediately<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
<br />
// Can this player play?<br />
<br />
$possibleMoves = self::getPossibleMoves( $player_id );<br />
if( count( $possibleMoves ) == 0 )<br />
{<br />
<br />
// This player can't play<br />
// Can his opponent play ?<br />
$opponent_id = self::getUniqueValueFromDb( "SELECT player_id FROM player WHERE player_id!='$player_id' " );<br />
if( count( self::getPossibleMoves( $opponent_id ) ) == 0 )<br />
{<br />
// Nobody can move => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
}<br />
else<br />
{ <br />
// => pass his turn<br />
$this->gamestate->nextState( 'cantPlay' );<br />
}<br />
}<br />
else<br />
{<br />
// This player can play. Give him some extra time<br />
self::giveExtraTime( $player_id );<br />
$this->gamestate->nextState( 'nextTurn' );<br />
}<br />
<br />
}<br />
</pre><br />
<br />
Now, when we play a disc, the rules are checked and the disc appears in the database.<br />
<br />
[[File:reversi9.jpg]]<br />
<br />
Of course, as we don't manage notifications on client side, we need to press F5 after each move to see the changes on the board.<br />
<br />
== Make the move appear automatically ==<br />
<br />
Now, what we have to do is process the notifications sent by the server and make the move appear on the interface.<br />
<br />
In our "setupNotifications" method, we register 2 methods for the 2 notifications we created at the previous step ('playDisc' and 'turnOverDiscs'):<br />
<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 );<br />
dojo.subscribe( 'turnOverDiscs', this, "notif_turnOverDiscs" );<br />
this.notifqueue.setSynchronous( 'turnOverDiscs', 1500 );<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
this.notifqueue.setSynchronous( 'newScores', 500 );<br />
</pre><br />
<br />
As you can see, we associate each of our 3 notifications with a method prefixed with "notif_". We also define these notifications as "synchronous", with a duration in millisecond. It tells the user interface to wait some time after executing the notification, to let the animation end before starting the next notification. In our specific case, the animation will be the following:<br />
* Make a disc slide from the player panel to its place on the board<br />
* (wait 500ms)<br />
* Make all turned over discs blink (and of course turned them over)<br />
* (wait 1500ms)<br />
* Update the player scores<br />
* (wait 500ms)<br />
<br />
The 2nd parameter in dodo.subscribe call (this) is the 'context', and will be passed in as a parameter to the specified method.<br />
<br />
Let's have a look now on the "playDisc" notification handler method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addTokenOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
No surprise here, we re-used some existing stuff to:<br />
* Remove the highlighted squares.<br />
* Add a new disc on board, coming from player panel.<br />
<br />
Now, here's the method that handles the turnOverDiscs notification:<br />
<br />
<pre><br />
notif_turnOverDiscs: function( notif )<br />
{<br />
// Get the color of the player who is returning the discs<br />
var targetColor = this.gamedatas.players[ notif.args.player_id ].color;<br />
<br />
// Make these discs blink and set them to the specified color<br />
for( var i in notif.args.turnedOver )<br />
{<br />
var token = notif.args.turnedOver[ i ];<br />
<br />
// Make the token blink 2 times<br />
var anim = dojo.fx.chain( [<br />
dojo.fadeOut( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeOut( { <br />
node: 'token_'+token.x+'_'+token.y,<br />
onEnd: function( node ) {<br />
<br />
// Remove any color class<br />
dojo.removeClass( node, [ 'tokencolor_000000', 'tokencolor_ffffff' ] );<br />
// ... and add the good one<br />
dojo.addClass( node, 'tokencolor_'+targetColor );<br />
<br />
} <br />
} ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } )<br />
<br />
] ); // end of dojo.fx.chain<br />
<br />
// ... and launch the animation<br />
anim.play(); <br />
}<br />
},<br />
</pre><br />
<br />
The list of the discs to be turned over has been made available by our server side code in "notif.args.turnedOver" (see previous paragraph). We loop through all these discs, and create a complex animation using dojo.Animation for each of them. The complete documentation on dojo animations [http://dojotoolkit.org/documentation/tutorials/1.6/animation/ can be found here].<br />
<br />
In few words: we create a chain of 4 animations to make the disc fade out, fade in, fade out again, and fade in again. At the end of the second fade out, we change the color of the disc. Finally, we launch the animation with "play()".<br />
<br />
And Also the notification to update the scores:<br />
<br />
<pre><br />
notif_newScores: function( notif )<br />
{<br />
for( var player_id in notif.args.scores )<br />
{<br />
var newScore = notif.args.scores[ player_id ];<br />
this.scoreCtrl[ player_id ].toValue( newScore );<br />
}<br />
},<br />
</pre></div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Create_a_game_in_BGA_Studio:_Complete_Walkthrough&diff=8445
Create a game in BGA Studio: Complete Walkthrough
2021-06-04T00:27:56Z
<p>JonChambers: /* Hook Input and Animation */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
This document is not a tutorial, but step by step instructions on how to build your own first game adaptation using BGA Studio framework.<br />
<br />
Before you read this material, you must:<br />
* Read the overall presentations of the BGA [[Studio]].<br />
* Some-what know the languages used by BGA Studio: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* Create a game using one of the available tutorials. Don't bother with a new game if you have not completed at least one of the tutorials.<br />
<br />
<br />
If you are stuck or have questions about this page post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum].<br />
If you're uncomfortable posting on the public forum you can send messages directly to developers who post answers on that forum but NOT the BGA admins.<br />
If you find typos in this wiki - fix it.<br />
<br />
== Select a First Game ==<br />
<br />
For your first '''real''' game you must either<br />
* Select a game from [https://en.studio.boardgamearena.com/licensing Available Licenses]<br />
* Or from the Public Domain<br />
<br />
But what if the game you want is not there? If you are able to successfully publish your first game, you would gain the trust of the BGA admins and they will be happy to assist you in obtaining a license for a game you really want to do or you can request a license yourself. You can read more about game licenses on [[BGA Game licenses]] page.<br />
<br />
Once you selected the game but before creating a new project, please take a few seconds to check that someone is not already developing this game. If it is the case, maybe you can propose to join the project?<br />
<br />
[http://en.studio.boardgamearena.com/#!projects Check the list of current projects]<br />
<br />
Even if you see a few projects with name of the game they may not be active. There are a lot of abandoned game projects. If it's not clear by the status, post to Developers forum asking if anybody actively working on the project or send a message to developers listed for the abandoned projects, and at the same time ask admins on the same forum post to send you graphics for that game if they have them (there a button on [https://en.studio.boardgamearena.com/licensing Available Licenses] page to request graphics, but it will just send email).<br />
<br />
If your goal was to fix bugs in an existing project, first try to locate on studio, projects developed by bga admins are not in the studio. Then get read only access to the project and you can create your own as a copy of the existing one. Contact existing project admin about getting write access to the original project or if they willing to take your patches - apply them.<br />
<br />
If you want to take over an existing project first ask on forum to see if project is abandoned, then get read only access (via project list) and see if this worth using it, if it has no code or graphics just start from the scratch, don't worry about project name it can be renamed later.<br />
<br />
== Create a project ==<br />
<br />
If you have not already, you have to create a project in BGA Studio for this game. If the original game name is taken use gamenameYOURINITIALS<br />
template, i.e."heartsla". Don't worry too much about the name, if game would be good enough to be publish it will be renamed to original name. <br />
<br />
Find and start the game in turn based mode, make sure it works.<br />
<br />
Second, modify the text in .tpl file, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup [http://en.doc.boardgamearena.com/Tools_and_tips_of_BGA_Studio#File_Sync FTP auto-sync] yet, do it now, manually copying files is a no-starter.<br />
<br />
Update your project status in [http://en.studio.boardgamearena.com/#!studio Control Panel > Manage games] page, you can say "development started" or "waiting for license" or "waiting for graphics" or combination of those.<br />
<br />
<br />
<br />
== Development Tools ==<br />
<br />
At some point you need to setup your development environment which consist of multiple tools, such as<br />
* Editor or IDE<br />
* Browser with dev tools<br />
* File sync tools<br />
* BGA Web tools<br />
* Image manipulation tools<br />
* Version control tools<br />
<br />
Please scan though articles from [[Studio#BGA_Studio_user_guide]] especially related to debugging and tools, there is a lot of useful info there.<br />
<br />
== Hook version control system ==<br />
<br />
If its a real game I would commit the code to version control right at start. You going to find yourself in the situation<br />
when game does not even start anymore and no way of debugging it unless you have a way to revert. That is where version control becomes very handy.<br />
If you don't know what I am talking about then at least back-up your files after each of major steps. Starting now.<br />
You can also create a project on github, but make sure '''you don't commit original publisher graphics files''' and '''you don't include a file with your sftp password''' (github is automatically crawled for passwords by hackers; a hacking attempt occurred on BGA studio for this reason in June 2020).<br />
You can (and should) also commit your modification periodically via studio's control panel.<br />
<br />
== Obtain game graphics ==<br />
<br />
If you developing a game from Available Licenses games, ask the admins to send you graphics by contacting studio@boardgamearena.com. While that request is being processed (it can take time, as it often requires some back and forth between the admins and the publishers) you can proceed to next step - project creation.<br />
<br />
If you don't get original graphics you go to '''Scavenger Hunt'''<br />
<br />
* If you developing a public domain card game you can borrow standard cards graphics from hearts project (see [[Tutorial hearts]]).<br />
* Standard game pieces - meeples, cubes, dice can be found here https://github.com/elaskavaia/bga-sharedcode/tree/master/img<br />
* Go to boardgamegeek.com find your game and obtain 3D game box image, 2D box image, and if you lucky they also sometime have boards and token scans in "Game Pieces" section of Images<br />
* If that fail google "boardgame <name>" and check Images section<br />
* Get the rules PDF as well, there tools that allows you to extract graphics from PDF, which usually good for meeples, cubes and such<br />
<br />
Once you get the graphics one way or another you have to massage it to fit in the BGA criteria, which usually involves<br />
* If publisher sends graphics in one token/card per file mode, you have to stitch them in sprite and scale down<br />
* For non square tiles and game pieces you need transparency<br />
* Usually you chop off scoring "ring" around the board of the game since scoring track not needed for online adaptation<br />
<br />
More details about graphics requirements can be found here [[Game art: img directory]].<br />
<br />
[[File:Rrr_search.png]]<br />
<br />
== Obtain game documentation ==<br />
<br />
Also at this time obtain a electronic copy of rules, such as PDF (English version). <br />
<br />
Also grab any other documents you may find on boardgamegeek such as FAQ, additional Reference books, and user created assistant documents, such<br />
as cheat-sheets (may be easier to get a data from these then trying to scrub pdf). You create and place them in the doc/ folder of the project then<br />
exclude them from version control. There is also a misc/ folder now but it will hold up to 1 Mb of data files which would be checked in, so rules pdf's may not fit there.<br />
<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet start with making sure game looks descent in the game selector, meaning it has nice box graphics and information is correct. <br />
<br />
For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
What you would do for real game you would go to http://boardgamegeek.com find the game and use the information from web-site to fill the gameinfos.<br />
<br />
<br />
The next step is to replace game_box.png with proper images, usually you can find all images including publisher logo on boardgamegeek website.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now important step. You have to LOAD these files in studio website through control panel. So go to Control Panel -> Manager Games -> YOURPROJECT<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
[[File:Gamepanel_sharedcode.png]]<br />
<br />
Now try to start the game again. If you some-how introduced a syntax error in gameinfos file it may not actually work (game won't start).<br />
Always use "Express Start" button to start the game. You should see a standard state prompt from template. You should see X players on the right, testdude0 .. testdudeX-1.<br />
To switch between them press the red arrow button near their names, it will open another tab. This way you don't need to login and logout from multiple accounts!<br />
<br />
== Fix source copyright ==<br />
<br />
Now since you have your own project, you want put your name in the copyright header, so replace<br />
<br />
© <Your name here> <Your email address here><br />
with<br />
© John Snow <jsnow@gameofthrones.com><br />
<br />
Well not exactly this but whatever your real name is. For all files in project directory, its about 10 files. Make sure project still starts after that :)<br />
<br />
== Reduce the Rules ==<br />
<br />
Programming a game will take a lot more time than you may think. Most of the projects in the studio are abandoned because of lack of patience or skill.<br />
To keep sane, start the game with *reduced* rules and try to complete that first.<br />
<br />
* If it has any expansions - do not even attempt to deal with them, not even - "I will just add graphics for them now and not use" - waste of time if you don't complete basic<br />
* If it has advanced rules - start with basic rules only, i.e. "beginner game"<br />
* If it has special rules for 2 player vs 4, start with most basic form (i.e. 4), restrict to 4 players <br />
* If it has 50 unique cards of 2 each - start with 2 unique cards with 25 each (just to keep it moving)<br />
* Any sort of rules that you think can be removed and not included in base - set aside for now <br />
* Ignore any sort of cool animations - dice rolling, card flipping, choo-choo sounds of the trains - all this fluff can be added later<br />
<br />
<br />
== Design Game Elements ==<br />
Technically game elements are already designed by board game designer but your job is to map it to program space.<br />
Each physical piece (card, token, cube) will leave footprints all over the code (unfortunatly in multiple disconnected places).<br />
To prepare the game you need to sort out these elements, i.e. categorize. I usually have to the following categorization (in object oriented view):<br />
* Instance - all individual pieces are instances, i.e. two red cubes are two instances of 'red cube' type (class)<br />
* Type - element type which distinctly represents that element in appearence (i.e. red cube is different type than blue cube)<br />
* Super Type - one of more common types that similar properties (i.e. red OR cube)<br />
* Player color - supertype specific for player color (sometimes there is no colors but like player 1 - but is conceptually the same, I use color because its easier to track)<br />
<br />
Personally I like to encode my elements in string using reverse dns notation listing all the properties above, i.e.<br />
meeple_ff0000_7 - this is instance #7 of type meeple_ff0000 (red meeple)<br />
Or<br />
card_yellow_magic_2 - this is instance #2 of yellow card (in this case yellow is color of deck not related to player color) that can do magic<br />
<br />
So every game element would be in the<br />
<br />
1. Database - instances. The db record would be something like <br />
key|location|state<br />
meeple_ff0000_7|slot_action_2|1<br />
meeple_ff0000_2|tableau_ff0000|0<br />
2. Material file - types and supertypes, we never need repearing info here, so never list individual instances but only types or supertypes, in this case we don't really need to define red meeple vs blue meeple<br />
'meeple'=>{'name'=>totranslate('Meeple')}<br />
3. Client (js, css, tpl, etc) - instances and types. For example my meeple will be like <br />
<pre><br />
<div id="meeple_ff0000_7" class="meeple meeple_ff0000"></div><br />
</pre><br />
with .css something like<br />
.meeple { background-image: url(img/tokens.png); width: 2em; height: 2em;}<br />
.meeple_ff0000 {background-position: 20% 0%;}<br />
4. Game php - setup and logic. During setup you have to generate all the pieces and place them in right positions. Also sometimes you need to reference elements to code the logic (I usually try to encode all rules in material file as much as possible)<br />
<br />
For complex card games I think it is the best to keep all these info and rules in spreadsheet and generate other files such as material.inc.php.<br />
See more info below about design of the individual layers.<br />
<br />
== Create Initial Layout and Game Graphics ==<br />
<br />
Mentally it is easier to start with game layout and graphics pieces. Even when nothing is working its give you moral satisfaction!<br />
<br />
There are a few ways how the html could have been generated. You could have started with nothing and generate<br />
it all by java script, or you could have started with complete game markup in html and make java script just hide and move pieces around. BGA framework also provides a third way, which is mix of both, plus a template engine to generate HTML using php. The only thing that is really annoying about the template engine is<br />
that you cannot put any translatable strings in the template (which means any visible text at all). If you are using the template approach all strings have to extracted as variables and injected through php (.view.php). This page explains the template engine in great detail:[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl|Template Engine]].<br />
<br />
The other disadvantage of the template engine is you cannot run and debug it locally, in the beginning of development it's a lot faster run off local pages, <br />
you can do it with some trickery described here [[Tools_and_tips_of_BGA_Studio#Speed_up_CSS_development_and_layout|Tools and Tips for BGA Studio]]<br />
<br />
During this step you have to decide what technical solutions you will be using, such as<br />
* Use inline positioning of all moving pieces, controlled by JS. There are a few classes that already exist in Studio to help with that (see [[Studio#Game_interface_.28Client_side.29|Game Interface - Client Side]]). OR use html/css layout engine to position pieces (my personal choice).<br />
* Use BGA template engine OR create all ui elements by JS OR manually write or generate complete html markup. The game usually contain 200-300 pieces, it seems wrong but actually its faster to type all of this up in html/css when trying write than debug code for page generator.<br />
Static HTML markup also means you have to use players color or abstracted player number (such as red is 1, blue is 2) not player id's anywhere in JS, since player id is dynamic by nature.<br />
<br />
Start by creating and mapping all games assets, best way is probably to open rule book on "boardgame contents" page and go through every piece. Every pieces of boardgame would have its "print" in multiple files in your game:<br />
* Some sort if "div" in html, where id of element match id of element in database (easiest way)<br />
* Css for the element (either unique or for class), usually with background propery refering to part of sprite image<br />
* Entry in material.inc.php referring to static properties of the element, i.e. name, tooltip, rules, etc<br />
* Entry in .tpl file to represent static or initial location on the table OR creation template<br />
<br />
Here are some specific examples:<br />
<br />
'''Game Board'''<br />
<br />
Create entry in .tpl file for the board, it will be static entry as we never need to create this dynamically<br />
<pre><br />
<div id="board" class="board shadow board4p"> ... </div><br />
</pre><br />
Create entry in .css file for this board and other board variants (in example below we have 4 ppl board whcih is diffrent than 2 ppl board)<br />
<pre><br />
.board {<br />
position: relative;<br />
width: 980px;<br />
height: 433px;<br />
margin-bottom: 5px;<br />
}<br />
<br />
.board4p {<br />
background-image: url(img/board4p.jpg);<br />
}<br />
</pre><br />
That wold be pretty much it for the board itself, as it does not really need a tooltip so we don't need entry in material.inc.php<br />
<br />
<br />
'''Game Board Slots'''<br />
<br />
These are interactive areas on the board, usually illustrated as such. In most cases you can get away with rectangular shapes, but sometimes you have to create circle or oval shapes (and in really advanced case would be some svg paths). For slots you can do the following:<br />
<br />
Entry in material.inc.php<br />
<pre><br />
$this->token_types = array(<br />
...<br />
'slot_action_2' => array(<br />
'type' => 'slot_action',<br />
'name' => clienttranslate("2 Gray Track Advancements"),<br />
'tooltip' => clienttranslate("This action gives you two advancements of gray track. You cannot use this action if you cannot complete all advancements."),<br />
'o'=>"1,0,0,gg", // automatic rules<br />
),<br />
...<br />
</pre><br />
<br />
Entry in template inside the "board" div<br />
<pre><br />
<div id="slot_action_2" class="slot_action_2 slot_action slot_w_1 slot"></div><br />
</pre><br />
<br />
Entry in .css with absolute position within the board (its actually better to use percentage - would be easier to scale later)<br />
<pre><br />
.slot_action_2 {<br />
top: 83px;<br />
left: 37px;<br />
}<br />
.slot_action {<br />
position: absolute;<br />
width: 46px;<br />
height: 26px;<br />
padding: 9px 7px 6px 4px;<br />
}<br />
</pre><br />
<br />
'''Meeples''' - also cards, tokens, other mobile stuff<br />
<br />
In css these guys will use "sprite" images with transparency, so it will look like this this<br />
<pre><br />
.meeple {<br />
background-image: url(img/tokens.png);<br />
width: 25px;<br />
height: 25px;<br />
}<br />
.meeple_ff0000 { /* red */<br />
background-position: 14% 0%;<br />
}<br />
</pre><br />
As for creation you can either generate them using template (where whole thing wrapped in template block and {COLOR} replace with all possible colors in .view.php<br />
<pre><br />
<div id="meeple_{COLOR}_1" class="meeple meeple_{COLOR} meepleable"></div><br />
<div id="meeple_{COLOR}_2" class="meeple meeple_{COLOR} meepleable"></div><br />
...<br />
</pre><br />
<br />
Or you can declare a template js var in .tpl file <br />
<pre><br />
var jstpl_mepple = '<div id="meeple_${color}_${num}" class="meeple meeple_${color} meepleable"></div>'; // this is in .tpl file at the bottom<br />
</pre><br />
and create in js, like this<br />
<pre><br />
var tokenDiv = this.format_block('jstpl_mepple', {<br />
"color" : color,<br />
"num" : i<br />
}); // this in js code somewhere before placing it<br />
</pre><br />
If you dealing with cards and decks, there are pre-build components that can generate stuff for you.<br />
<br />
When do you create dom element matching game element?<br />
* If you have static layout you create it in .tpl file and its always there, but during initial setup or during notification it moved in proper spot (including "removed from the game" spot)<br />
* If you dynamically generated pieces you create the element during notification, and sometimes during animation. Also don't forgot to hook event listener to it if its interactive.<br />
<br />
<br />
One of the greatest parts about the web is all client side code can be viewed in your browser, so if you wondering how something is done in another BGA game just load the page and spy on it! In Chrome that would be right click and "Inspect Element". That would immediately show html of the given element alongside with css used for it (on the right). Another great way to learn is you can add yourself to any BGA project as read only from the project page!<br />
<br />
So at the end of this stage you should complete the following (keeping in mind reduced rules/material for first iteration):<br />
* Create a layout of the game, with positioning of main board, player areas, zones, other supporting areas, etc<br />
* Create css and html snippets for all game pieces: boards, tokens, meeples, etc. Place them all in initial template (even if they're not supposed to be visible at start). I.e. create fake player's hand with cards, put meeples on the board<br />
* Hook layout to number of players and colors picked by the game and test with multiple players<br />
* Figure out what you want to display in mini-player boards and hook it up<br />
* Create material.inc.php and populate with initial values (names, tooltips, rules) for all relevant game elements or classes of elements<br />
<br />
If at this time you don't have graphics yet create pieces with just css, you can use shape, background color and object text using css ::after construct to fake the pieces.<br />
<br />
<br />
[[File:Injected_text.png]]<br />
<br />
== Hook Input and Animation ==<br />
<br />
This step can be done before or after some of the server steps, or you go in iterations switching back and forward until you get it done, up to you.<br />
<br />
At this time you want to hook clicking on pieces and buttons and provide some reaction, such of moving a piece. The handler code will be replaced later by the server hook, but at the beginning you want you game to be alive as early as possible. <br />
<br />
Usually all pieces will be hooked to onclick during JS "setup" method, in addition if you create elements during server notification they have to be hooked up at that time.<br />
<br />
You can play with animation effects you want put in place, in general all the pieces that move in real game should be moving, such as meeples, resources tokens/cubes, cards, vp tokens. <br />
Regular piece animation is provided by BGA framework, but if you use html layout positioning not inline positioning you have to remove absolute positions (inline position styling) after each move. The set of functions for relative position token animation can found in https://github.com/elaskavaia/bga-sharedcode/blob/master/sharedcode.js <br />
<br />
Also its a good idea to give player a visual cues on what game elements are clickable now, usually it will be a style, such as "active_slot", with visual effect of white dashed outline (outline is better then border, because border changes will make piece slightly move since it changes the size) or box-shadow (i.e. neon glow)<br />
<br />
If you read [http://www.slideshare.net/boardgamearena/bga-studio-guidelines BGA developers guidelines] you know that you should not get carried away with animation, you creating a board game not a video game... That also applies to sound effects (in general, you should not use any sounds effects beside already provided by framework).<br />
<br />
See [[Game_interface_logic:_yourgamename.js#Players_input|Player's Input]] and [[Game_interface_logic:_yourgamename.js#Access_and_manipulate_the_DOM|Animation and DOM Manipulation]] for JS reference.<br />
<br />
== Create Database Schema ==<br />
<br />
At some point you have to design your game database. Do it sooner then later since it would be harder to change it later, since some<br />
code decisions would be based on that.<br />
<br />
If you have grid-based abstract game use template from reversi, if you have a card game use template from hearts (the cards one also commented out in generated template for your project). The cards database goes with php class called [[Deck]].<br />
<br />
In general make it as simple as possible. <br />
Think about it, your game has 300 pieces (likely less). Using database to store this amount of data is like shooting a mosquito with a tank.<br />
Anything more complex then one table with 5 columns or two tables will only going to make it harder to develop and not improve performance.<br />
You can forget about normalising and any fancy stuff you learn about databases in school. String field for a primary key would be as fast as integer when we talking about this size of data. So don't over-optimize with trying to have integers field that have state based on bitmask!<br />
<br />
Also remember that static (non dynamic) information about the game does not need to be stored in the database, that all include everything that does not change, i.e<br />
all token/card properties such as name, tooltips, "strength", color, etc. This is stored in material.inc.php and server has access to it from anywhere, as well as client<br />
if you send it with getAllDatas(). The only reason store some of it in database if it can affect your queries (i.e. type of token).<br />
<br />
Usually design process will contain the following steps:<br />
* Design game model - model that represent your game in progress, such as at any given step you can restore the game from that model<br />
* Mapping - now map real game to that model<br />
* Encoding - now represent this model in database and material file with reasonable amount of fields<br />
<br />
Example: '''The card game'''<br />
<br />
* In real word to "save" the game we take a picture a play area, save cards from it, then put away draw deck, discard and hand of each player separately and mark it, also we will record current scoring (if any) and who's turn was it<br />
* Framework handles state machine transition, so you don't have to worry about database design for that (i.e. who's turn it is, what phase of the game we are at, you still have to design it but as part of state machine step)<br />
* Also framework supports basic player information, color, order around the table, basic scoring, etc, so you don't have to worry about it either<br />
* The only thing you need in your database is state of the "board", which is "where each pieces is, and in what state", or (position,rotation) pair.<br />
* The card state is very simple, its usually "face up/face down", "tapped/untapped", "right side up/up side down"<br />
* As position go we never need real x,y,z. We need to know what "zone" card was, and depending on the zone it may sometimes need an extra "z" or "x" as card order. The zone position itself usually static or irrelevant.<br />
* So our model is: we have cards, which have some attributes, at any given point in time they belong to a "zone", and can also have order and state<br />
* Now for mapping we should consider what info changes and what info is static, static info is always candidate for material file or html<br />
* For dynamic stuff we should try to reduce amount of fields we need, i.e. we need a field for card, so its one, we need to know what zone cards belong to, its 2, and we have possible few other fields, but if you look closely at you game you may find out that most of the zone only need one attribute at a time, i.e. draw pile always have cards face down, hand always face up, also for hand and discard order does not matter at all (but for draw it does matter). So in majority of cases we can get away with one single extra integer field representing state or order<br />
* In real database both card and zone will be integers as primary keys referring to additional tables, but in our case its total overkill, so they can be strings as easily<br />
<br />
You can also use cards database schema and [[Deck]] implementation for most purposes (even you not dealing with cards).<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
Another Example: '''The euro game'''<br />
<br />
See details on database design for euro game at [[BGA_Studio_Cookbook#Database_for_The_euro_game]]<br />
<br />
<br />
So the piece mapping for non-grid based <br />
games can be in most case represented by (string: token_key, string: location, int: state), example of such database schema can be found here:<br />
[https://github.com/elaskavaia/bga-sharedcode/blob/master/dbmodel.sql dbmodel.sql] and class implementing access to it here [https://github.com/elaskavaia/bga-sharedcode/blob/master/modules/tokens.php tokens.php].<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `token` (<br />
`token_key` varchar(32) NOT NULL,<br />
`token_location` varchar(32) NOT NULL,<br />
`token_state` int(10),<br />
PRIMARY KEY (`token_key`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8;<br />
</pre><br />
<br />
<br />
See [[Game database model: dbmodel.sql]] for details about editing the file.<br />
<br />
Note: the simpler the database is the less debugging of db issues you have to deal with including database migration. The tokens database above - if you use it you never have to worry about migration because you don't need extra tables in 95% of the games.<br />
Here are some example of how real games are mapped to such database:<br />
<br />
'''Chess''' <br />
<br />
- chess is grid base game and normally you would use positional columns, but just for the sake of argument, the chess game will look like this<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|Q_white<br />
|f3<br />
|0<br />
|-<br />
|P_black_2<br />
|c6<br />
|0<br />
|-<br />
|K_black<br />
|e8<br />
|1<br />
|}<br />
And the state in this case indicated that kind was moved for example (which means castling cannot be performed)<br />
<br />
<br />
'''Classic card game''' <br />
<br />
Lets pretend we need 2 decks for that game<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|Q_spades_1<br />
|hand_ff0000<br />
|0 /* state not used for hand */<br />
|-<br />
|10_hearts_2<br />
|tableau_ff0000<br />
|2 /* position */<br />
|-<br />
|10_hearts_1<br />
|tableau_common<br />
|1 /* face down */<br />
|}<br />
<br />
'''Eminent Domain (card game)''' <br />
<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|card_tech_23<br />
|hand_ff0000<br />
|0 /* state not used for hand */<br />
|-<br />
|card_planet_19<br />
|tableau_ff0000<br />
|1 /* face up */<br />
|-<br />
|reource_s_22 /* silicon */<br />
|card_planet_19<br />
|2 /* production state */<br />
|-<br />
|fighter_F_1<br />
|tableau_ff0000<br />
|0<br />
|}<br />
<br />
<br />
You can also look at other games that use Tokens database and access layer: Nippon, Dungeon Petz, Lewis & Clark, Battleship, Russian Railroads, Khronos<br />
<br />
== Implement Game Setup ==<br />
<br />
Once you have your database schema you can do a proper game setup. Usually you open rulebook on the "Game Setup" page<br />
and implement these step by step populating the database (using db access API).<br />
Game initialization is performed in php method setupNewGame, this method is called once when game table is created.<br />
Game notifications cannot be sent during this time.<br />
<br />
== Implement One time game model synchronisation ==<br />
<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in UI, so we fix getAllDatas function<br />
to return all possible data we need to reconstruct the game. The template for getAllDatas already taking care of player info, but you <br />
have to alter it to return all other data from database visible to the "current" player.<br />
<br />
After that on the client side we should display this data, so in your .js file in setup function (which is the receiver of getAllDatas) you add calls that handle data send by server, usually by calling animation function such as "placeToken" or "placeCard".<br />
<br />
== Create State Machine ==<br />
<br />
Now you need to create a game state machine. <br />
<br />
The state handling spread across 4 files, so you have to make sure all the pieces are connected together.<br />
The state machine states.inc.php defines all the states, and function handlers on php side in a form of string,<br />
and if any of these functions are not implemented it would be very hard to debug because it will break in random places.<br />
<br />
Please first watch this again [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine BGA game state machine]<br />
and then please read [[Your game state machine: states.inc.php]].<br />
<br />
Now the state machine should be relatively simple. If you find yourself with machine with more than 20 states its probably not the way to go.<br />
Not all the player interactions need separate states, a lot of things can be implemented directly on client, i.e. if your player need to select<br />
a reward token, which offers choice of resource, instead of two states on server just have one state on server and possible few states on client (client side states)<br />
to collect this info.<br />
<br />
== Handle Turn Order ==<br />
<br />
If your game goes in clockwise order in natural sitting position nothing really needed you just use standard API and you are good. However if position is complecated<br />
it may require some trickery.<br />
<br />
Usually turn order is done by "game state" (see state machine above). Basically it would be two choices:<br />
* Turn order depends on game situation (such as we take player with highest number of red cubes)<br />
* Turn order is custom and assign on previos step - i.e. we not playing in clockwise order anymore. In this case you either need to extend player table with new order info (cannot! use player_no column) or use natural order markers (i.e. marker_ff0000 on position_1). In this we can buid player array in right order and pick next player based on previous player using existing helper function as $this->createNextPlayerTable<br />
<br />
== Implement Notification handling ==<br />
<br />
Now to implement things for real we have hook UI actions to ajax calls, and process notifications send by server.<br />
So previously we hooked onclick js handler right to client animation, in real game its a two<br />
step operation. When user clicks on something, client sends an ajax call to server, server processes it and updates database, server sends<br />
notification in response, client hooks animations to server notification. See [[Game_interface_logic:_yourgamename.js#Notifications|JS Notifications]].<br />
<br />
Exception to this is client states, if you need to process two step user interaction such as select meeple, place meeple, you may want <br />
to avoid sending data to server until step is complete (which may involve direct client side animation). See [[BGA_Studio_Cookbook#Multi_Step_Interactions:_Select_Worker.2FPlace_Worker_-_Using_Selection|Multi-Step Interactions]]<br />
<br />
Part of the sending notifications would be to update player's scoring, BGA uses standard control for score (on JS side), see [[Game_interface_logic:_yourgamename.js#Update_players_score|Update Player's Score]].<br />
<br />
In BGA there is only two ways interact with the server (officially)<br />
* Initial data dump - when JS client starts it gets all current data via setup() method<br />
* Game actions - ajaxcall from client, it returns error or ok (not data), then server send butch of notifications to client<br />
<br />
Note current ajaxcall is super vebosy and prone to errors, I suggest to use helper function. It does a lot of stuff you must do anyways.<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) args = []; // this allows to skip args parameter for action which do not require them<br />
<br />
args.lock = true; // this allows to avoid rapid action clicking which can cause race condition on server<br />
<br />
if (this.checkAction(action)) { // this does all the proper check that player is active and action is declared<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args, // this is mandatory fluff <br />
this, (result) => { }, // success result handler is empty - it is never needed<br />
handler); // this is real result handler - it called both on success and error, its is optional param - you rarely need it<br />
}<br />
},<br />
</pre><br />
<br />
When you insert a single action you have to update multiples files:<br />
* in ggg.js add ajaxcall, i.e. something like <br />
this.addActionButton('pass',_('Pass),()=>this.ajaxcallwrapper('pass'));<br />
* in states.php - add action 'pass' to list of possible actions<br />
'possibleactions' => ['pass','playCard']<br />
* in action.php - add action hander, see https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php<br />
* in game.php - add action hander, there you must do the following<br />
** call checkAction to validate the action<br />
** possible do more game specific check to validate what player doing is legal (even its not possible from your js side - player can cheat - not allow that)<br />
** do some database maniplations, using access api<br />
** send notifications - this is the "reply" for action<br />
** transition to new state (it very rare that user will remain in the same state, except for multi-active states)<br />
* back to ggg.js add notification subsciption and notification handler (two separate things)<br />
<br />
== Wrap Up ==<br />
<br />
<br />
* Implement game progression (getGameProgression() in php)<br />
* Implement Zombie turn (zombieTurn() in php)<br />
* Define and implemented some meaningful statistics for your game (i.e. total points, point from source A, B, C...)<br />
* The games logs should explain what happened if player was not looking<br />
* You need to implemented tiebreaking (using aux score field) and updated tiebreaker description in meta-data<br />
* Make sure all UI strings are marked for translation<br />
* UI elements which are images (i.e. tokens, cards) should have tooltips<br />
<br />
<br />
When you think you game is completely working there is still bunch of stuff you have to do/check before telling admin that game is ready, please go though this [[Pre-release checklist]].<br />
<br />
Finally, visit the game page for your alpha game (https://boardgamearena.com/gamepanel?game=…) to add the following information if you can:<br />
* Links to the rules (in multiple languages if available).<br />
* Links to teaching videos.<br />
* In the "On the web" section, links to:<br />
** The official website for the game (if there is one).<br />
** The BoardGameGeek page for the game.<br />
* Consider writing a summary of the rules.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Create_a_game_in_BGA_Studio:_Complete_Walkthrough&diff=8444
Create a game in BGA Studio: Complete Walkthrough
2021-06-04T00:25:41Z
<p>JonChambers: /* Create Initial Layout and Game Graphics */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
This document is not a tutorial, but step by step instructions on how to build your own first game adaptation using BGA Studio framework.<br />
<br />
Before you read this material, you must:<br />
* Read the overall presentations of the BGA [[Studio]].<br />
* Some-what know the languages used by BGA Studio: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* Create a game using one of the available tutorials. Don't bother with a new game if you have not completed at least one of the tutorials.<br />
<br />
<br />
If you are stuck or have questions about this page post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum].<br />
If you're uncomfortable posting on the public forum you can send messages directly to developers who post answers on that forum but NOT the BGA admins.<br />
If you find typos in this wiki - fix it.<br />
<br />
== Select a First Game ==<br />
<br />
For your first '''real''' game you must either<br />
* Select a game from [https://en.studio.boardgamearena.com/licensing Available Licenses]<br />
* Or from the Public Domain<br />
<br />
But what if the game you want is not there? If you are able to successfully publish your first game, you would gain the trust of the BGA admins and they will be happy to assist you in obtaining a license for a game you really want to do or you can request a license yourself. You can read more about game licenses on [[BGA Game licenses]] page.<br />
<br />
Once you selected the game but before creating a new project, please take a few seconds to check that someone is not already developing this game. If it is the case, maybe you can propose to join the project?<br />
<br />
[http://en.studio.boardgamearena.com/#!projects Check the list of current projects]<br />
<br />
Even if you see a few projects with name of the game they may not be active. There are a lot of abandoned game projects. If it's not clear by the status, post to Developers forum asking if anybody actively working on the project or send a message to developers listed for the abandoned projects, and at the same time ask admins on the same forum post to send you graphics for that game if they have them (there a button on [https://en.studio.boardgamearena.com/licensing Available Licenses] page to request graphics, but it will just send email).<br />
<br />
If your goal was to fix bugs in an existing project, first try to locate on studio, projects developed by bga admins are not in the studio. Then get read only access to the project and you can create your own as a copy of the existing one. Contact existing project admin about getting write access to the original project or if they willing to take your patches - apply them.<br />
<br />
If you want to take over an existing project first ask on forum to see if project is abandoned, then get read only access (via project list) and see if this worth using it, if it has no code or graphics just start from the scratch, don't worry about project name it can be renamed later.<br />
<br />
== Create a project ==<br />
<br />
If you have not already, you have to create a project in BGA Studio for this game. If the original game name is taken use gamenameYOURINITIALS<br />
template, i.e."heartsla". Don't worry too much about the name, if game would be good enough to be publish it will be renamed to original name. <br />
<br />
Find and start the game in turn based mode, make sure it works.<br />
<br />
Second, modify the text in .tpl file, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup [http://en.doc.boardgamearena.com/Tools_and_tips_of_BGA_Studio#File_Sync FTP auto-sync] yet, do it now, manually copying files is a no-starter.<br />
<br />
Update your project status in [http://en.studio.boardgamearena.com/#!studio Control Panel > Manage games] page, you can say "development started" or "waiting for license" or "waiting for graphics" or combination of those.<br />
<br />
<br />
<br />
== Development Tools ==<br />
<br />
At some point you need to setup your development environment which consist of multiple tools, such as<br />
* Editor or IDE<br />
* Browser with dev tools<br />
* File sync tools<br />
* BGA Web tools<br />
* Image manipulation tools<br />
* Version control tools<br />
<br />
Please scan though articles from [[Studio#BGA_Studio_user_guide]] especially related to debugging and tools, there is a lot of useful info there.<br />
<br />
== Hook version control system ==<br />
<br />
If its a real game I would commit the code to version control right at start. You going to find yourself in the situation<br />
when game does not even start anymore and no way of debugging it unless you have a way to revert. That is where version control becomes very handy.<br />
If you don't know what I am talking about then at least back-up your files after each of major steps. Starting now.<br />
You can also create a project on github, but make sure '''you don't commit original publisher graphics files''' and '''you don't include a file with your sftp password''' (github is automatically crawled for passwords by hackers; a hacking attempt occurred on BGA studio for this reason in June 2020).<br />
You can (and should) also commit your modification periodically via studio's control panel.<br />
<br />
== Obtain game graphics ==<br />
<br />
If you developing a game from Available Licenses games, ask the admins to send you graphics by contacting studio@boardgamearena.com. While that request is being processed (it can take time, as it often requires some back and forth between the admins and the publishers) you can proceed to next step - project creation.<br />
<br />
If you don't get original graphics you go to '''Scavenger Hunt'''<br />
<br />
* If you developing a public domain card game you can borrow standard cards graphics from hearts project (see [[Tutorial hearts]]).<br />
* Standard game pieces - meeples, cubes, dice can be found here https://github.com/elaskavaia/bga-sharedcode/tree/master/img<br />
* Go to boardgamegeek.com find your game and obtain 3D game box image, 2D box image, and if you lucky they also sometime have boards and token scans in "Game Pieces" section of Images<br />
* If that fail google "boardgame <name>" and check Images section<br />
* Get the rules PDF as well, there tools that allows you to extract graphics from PDF, which usually good for meeples, cubes and such<br />
<br />
Once you get the graphics one way or another you have to massage it to fit in the BGA criteria, which usually involves<br />
* If publisher sends graphics in one token/card per file mode, you have to stitch them in sprite and scale down<br />
* For non square tiles and game pieces you need transparency<br />
* Usually you chop off scoring "ring" around the board of the game since scoring track not needed for online adaptation<br />
<br />
More details about graphics requirements can be found here [[Game art: img directory]].<br />
<br />
[[File:Rrr_search.png]]<br />
<br />
== Obtain game documentation ==<br />
<br />
Also at this time obtain a electronic copy of rules, such as PDF (English version). <br />
<br />
Also grab any other documents you may find on boardgamegeek such as FAQ, additional Reference books, and user created assistant documents, such<br />
as cheat-sheets (may be easier to get a data from these then trying to scrub pdf). You create and place them in the doc/ folder of the project then<br />
exclude them from version control. There is also a misc/ folder now but it will hold up to 1 Mb of data files which would be checked in, so rules pdf's may not fit there.<br />
<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet start with making sure game looks descent in the game selector, meaning it has nice box graphics and information is correct. <br />
<br />
For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
What you would do for real game you would go to http://boardgamegeek.com find the game and use the information from web-site to fill the gameinfos.<br />
<br />
<br />
The next step is to replace game_box.png with proper images, usually you can find all images including publisher logo on boardgamegeek website.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now important step. You have to LOAD these files in studio website through control panel. So go to Control Panel -> Manager Games -> YOURPROJECT<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
[[File:Gamepanel_sharedcode.png]]<br />
<br />
Now try to start the game again. If you some-how introduced a syntax error in gameinfos file it may not actually work (game won't start).<br />
Always use "Express Start" button to start the game. You should see a standard state prompt from template. You should see X players on the right, testdude0 .. testdudeX-1.<br />
To switch between them press the red arrow button near their names, it will open another tab. This way you don't need to login and logout from multiple accounts!<br />
<br />
== Fix source copyright ==<br />
<br />
Now since you have your own project, you want put your name in the copyright header, so replace<br />
<br />
© <Your name here> <Your email address here><br />
with<br />
© John Snow <jsnow@gameofthrones.com><br />
<br />
Well not exactly this but whatever your real name is. For all files in project directory, its about 10 files. Make sure project still starts after that :)<br />
<br />
== Reduce the Rules ==<br />
<br />
Programming a game will take a lot more time than you may think. Most of the projects in the studio are abandoned because of lack of patience or skill.<br />
To keep sane, start the game with *reduced* rules and try to complete that first.<br />
<br />
* If it has any expansions - do not even attempt to deal with them, not even - "I will just add graphics for them now and not use" - waste of time if you don't complete basic<br />
* If it has advanced rules - start with basic rules only, i.e. "beginner game"<br />
* If it has special rules for 2 player vs 4, start with most basic form (i.e. 4), restrict to 4 players <br />
* If it has 50 unique cards of 2 each - start with 2 unique cards with 25 each (just to keep it moving)<br />
* Any sort of rules that you think can be removed and not included in base - set aside for now <br />
* Ignore any sort of cool animations - dice rolling, card flipping, choo-choo sounds of the trains - all this fluff can be added later<br />
<br />
<br />
== Design Game Elements ==<br />
Technically game elements are already designed by board game designer but your job is to map it to program space.<br />
Each physical piece (card, token, cube) will leave footprints all over the code (unfortunatly in multiple disconnected places).<br />
To prepare the game you need to sort out these elements, i.e. categorize. I usually have to the following categorization (in object oriented view):<br />
* Instance - all individual pieces are instances, i.e. two red cubes are two instances of 'red cube' type (class)<br />
* Type - element type which distinctly represents that element in appearence (i.e. red cube is different type than blue cube)<br />
* Super Type - one of more common types that similar properties (i.e. red OR cube)<br />
* Player color - supertype specific for player color (sometimes there is no colors but like player 1 - but is conceptually the same, I use color because its easier to track)<br />
<br />
Personally I like to encode my elements in string using reverse dns notation listing all the properties above, i.e.<br />
meeple_ff0000_7 - this is instance #7 of type meeple_ff0000 (red meeple)<br />
Or<br />
card_yellow_magic_2 - this is instance #2 of yellow card (in this case yellow is color of deck not related to player color) that can do magic<br />
<br />
So every game element would be in the<br />
<br />
1. Database - instances. The db record would be something like <br />
key|location|state<br />
meeple_ff0000_7|slot_action_2|1<br />
meeple_ff0000_2|tableau_ff0000|0<br />
2. Material file - types and supertypes, we never need repearing info here, so never list individual instances but only types or supertypes, in this case we don't really need to define red meeple vs blue meeple<br />
'meeple'=>{'name'=>totranslate('Meeple')}<br />
3. Client (js, css, tpl, etc) - instances and types. For example my meeple will be like <br />
<pre><br />
<div id="meeple_ff0000_7" class="meeple meeple_ff0000"></div><br />
</pre><br />
with .css something like<br />
.meeple { background-image: url(img/tokens.png); width: 2em; height: 2em;}<br />
.meeple_ff0000 {background-position: 20% 0%;}<br />
4. Game php - setup and logic. During setup you have to generate all the pieces and place them in right positions. Also sometimes you need to reference elements to code the logic (I usually try to encode all rules in material file as much as possible)<br />
<br />
For complex card games I think it is the best to keep all these info and rules in spreadsheet and generate other files such as material.inc.php.<br />
See more info below about design of the individual layers.<br />
<br />
== Create Initial Layout and Game Graphics ==<br />
<br />
Mentally it is easier to start with game layout and graphics pieces. Even when nothing is working its give you moral satisfaction!<br />
<br />
There are a few ways how the html could have been generated. You could have started with nothing and generate<br />
it all by java script, or you could have started with complete game markup in html and make java script just hide and move pieces around. BGA framework also provides a third way, which is mix of both, plus a template engine to generate HTML using php. The only thing that is really annoying about the template engine is<br />
that you cannot put any translatable strings in the template (which means any visible text at all). If you are using the template approach all strings have to extracted as variables and injected through php (.view.php). This page explains the template engine in great detail:[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl|Template Engine]].<br />
<br />
The other disadvantage of the template engine is you cannot run and debug it locally, in the beginning of development it's a lot faster run off local pages, <br />
you can do it with some trickery described here [[Tools_and_tips_of_BGA_Studio#Speed_up_CSS_development_and_layout|Tools and Tips for BGA Studio]]<br />
<br />
During this step you have to decide what technical solutions you will be using, such as<br />
* Use inline positioning of all moving pieces, controlled by JS. There are a few classes that already exist in Studio to help with that (see [[Studio#Game_interface_.28Client_side.29|Game Interface - Client Side]]). OR use html/css layout engine to position pieces (my personal choice).<br />
* Use BGA template engine OR create all ui elements by JS OR manually write or generate complete html markup. The game usually contain 200-300 pieces, it seems wrong but actually its faster to type all of this up in html/css when trying write than debug code for page generator.<br />
Static HTML markup also means you have to use players color or abstracted player number (such as red is 1, blue is 2) not player id's anywhere in JS, since player id is dynamic by nature.<br />
<br />
Start by creating and mapping all games assets, best way is probably to open rule book on "boardgame contents" page and go through every piece. Every pieces of boardgame would have its "print" in multiple files in your game:<br />
* Some sort if "div" in html, where id of element match id of element in database (easiest way)<br />
* Css for the element (either unique or for class), usually with background propery refering to part of sprite image<br />
* Entry in material.inc.php referring to static properties of the element, i.e. name, tooltip, rules, etc<br />
* Entry in .tpl file to represent static or initial location on the table OR creation template<br />
<br />
Here are some specific examples:<br />
<br />
'''Game Board'''<br />
<br />
Create entry in .tpl file for the board, it will be static entry as we never need to create this dynamically<br />
<pre><br />
<div id="board" class="board shadow board4p"> ... </div><br />
</pre><br />
Create entry in .css file for this board and other board variants (in example below we have 4 ppl board whcih is diffrent than 2 ppl board)<br />
<pre><br />
.board {<br />
position: relative;<br />
width: 980px;<br />
height: 433px;<br />
margin-bottom: 5px;<br />
}<br />
<br />
.board4p {<br />
background-image: url(img/board4p.jpg);<br />
}<br />
</pre><br />
That wold be pretty much it for the board itself, as it does not really need a tooltip so we don't need entry in material.inc.php<br />
<br />
<br />
'''Game Board Slots'''<br />
<br />
These are interactive areas on the board, usually illustrated as such. In most cases you can get away with rectangular shapes, but sometimes you have to create circle or oval shapes (and in really advanced case would be some svg paths). For slots you can do the following:<br />
<br />
Entry in material.inc.php<br />
<pre><br />
$this->token_types = array(<br />
...<br />
'slot_action_2' => array(<br />
'type' => 'slot_action',<br />
'name' => clienttranslate("2 Gray Track Advancements"),<br />
'tooltip' => clienttranslate("This action gives you two advancements of gray track. You cannot use this action if you cannot complete all advancements."),<br />
'o'=>"1,0,0,gg", // automatic rules<br />
),<br />
...<br />
</pre><br />
<br />
Entry in template inside the "board" div<br />
<pre><br />
<div id="slot_action_2" class="slot_action_2 slot_action slot_w_1 slot"></div><br />
</pre><br />
<br />
Entry in .css with absolute position within the board (its actually better to use percentage - would be easier to scale later)<br />
<pre><br />
.slot_action_2 {<br />
top: 83px;<br />
left: 37px;<br />
}<br />
.slot_action {<br />
position: absolute;<br />
width: 46px;<br />
height: 26px;<br />
padding: 9px 7px 6px 4px;<br />
}<br />
</pre><br />
<br />
'''Meeples''' - also cards, tokens, other mobile stuff<br />
<br />
In css these guys will use "sprite" images with transparency, so it will look like this this<br />
<pre><br />
.meeple {<br />
background-image: url(img/tokens.png);<br />
width: 25px;<br />
height: 25px;<br />
}<br />
.meeple_ff0000 { /* red */<br />
background-position: 14% 0%;<br />
}<br />
</pre><br />
As for creation you can either generate them using template (where whole thing wrapped in template block and {COLOR} replace with all possible colors in .view.php<br />
<pre><br />
<div id="meeple_{COLOR}_1" class="meeple meeple_{COLOR} meepleable"></div><br />
<div id="meeple_{COLOR}_2" class="meeple meeple_{COLOR} meepleable"></div><br />
...<br />
</pre><br />
<br />
Or you can declare a template js var in .tpl file <br />
<pre><br />
var jstpl_mepple = '<div id="meeple_${color}_${num}" class="meeple meeple_${color} meepleable"></div>'; // this is in .tpl file at the bottom<br />
</pre><br />
and create in js, like this<br />
<pre><br />
var tokenDiv = this.format_block('jstpl_mepple', {<br />
"color" : color,<br />
"num" : i<br />
}); // this in js code somewhere before placing it<br />
</pre><br />
If you dealing with cards and decks, there are pre-build components that can generate stuff for you.<br />
<br />
When do you create dom element matching game element?<br />
* If you have static layout you create it in .tpl file and its always there, but during initial setup or during notification it moved in proper spot (including "removed from the game" spot)<br />
* If you dynamically generated pieces you create the element during notification, and sometimes during animation. Also don't forgot to hook event listener to it if its interactive.<br />
<br />
<br />
One of the greatest parts about the web is all client side code can be viewed in your browser, so if you wondering how something is done in another BGA game just load the page and spy on it! In Chrome that would be right click and "Inspect Element". That would immediately show html of the given element alongside with css used for it (on the right). Another great way to learn is you can add yourself to any BGA project as read only from the project page!<br />
<br />
So at the end of this stage you should complete the following (keeping in mind reduced rules/material for first iteration):<br />
* Create a layout of the game, with positioning of main board, player areas, zones, other supporting areas, etc<br />
* Create css and html snippets for all game pieces: boards, tokens, meeples, etc. Place them all in initial template (even if they're not supposed to be visible at start). I.e. create fake player's hand with cards, put meeples on the board<br />
* Hook layout to number of players and colors picked by the game and test with multiple players<br />
* Figure out what you want to display in mini-player boards and hook it up<br />
* Create material.inc.php and populate with initial values (names, tooltips, rules) for all relevant game elements or classes of elements<br />
<br />
If at this time you don't have graphics yet create pieces with just css, you can use shape, background color and object text using css ::after construct to fake the pieces.<br />
<br />
<br />
[[File:Injected_text.png]]<br />
<br />
== Hook Input and Animation ==<br />
<br />
This step can be done before or after some of the server steps, or you go in iterations switching back and forward until you get it done, up to you.<br />
<br />
At this time you want to hook clicking on pieces and buttons and provide some reaction, such of moving a piece. The handler code will be replaced later by the server hook, but at the begging you want you game to be alive as early as possible. <br />
<br />
Usually all pieces will be hooked to onclick during JS "setup" method, in addition if you create elements during server notification they have to be hooked up at that time.<br />
<br />
You can play with animation effects you want put in place, in general all the pieces that move in real game should be moving, such as meeples, resources tokens/cubes, cards, vp tokens. <br />
Regular piece animation is provided by BGA framework, but if you use html layout positioning not inline positioning you have to remove absolute positions (inline position styling) after each move. The set of functions for relative position token animation can found in https://github.com/elaskavaia/bga-sharedcode/blob/master/sharedcode.js <br />
<br />
Also its a good idea to give player a visual cues on what game elements are clickable now, usually it will be a style, such as "active_slot", with visual effect of white dashed outline (outline is better then border, because border changes will make piece slightly move since it changes the size) or box-shadow (i.e. neon glow)<br />
<br />
If you read [http://www.slideshare.net/boardgamearena/bga-studio-guidelines BGA developers guidelines] you know that you should not get carried away with animation, you creating a board game not a video game... That also applies to sound effects (in general, you should not use any sounds effects beside already provided by framework).<br />
<br />
See [[Game_interface_logic:_yourgamename.js#Players_input|Player's Input]] and [[Game_interface_logic:_yourgamename.js#Access_and_manipulate_the_DOM|Animation and DOM Manipulation]] for JS reference.<br />
<br />
== Create Database Schema ==<br />
<br />
At some point you have to design your game database. Do it sooner then later since it would be harder to change it later, since some<br />
code decisions would be based on that.<br />
<br />
If you have grid-based abstract game use template from reversi, if you have a card game use template from hearts (the cards one also commented out in generated template for your project). The cards database goes with php class called [[Deck]].<br />
<br />
In general make it as simple as possible. <br />
Think about it, your game has 300 pieces (likely less). Using database to store this amount of data is like shooting a mosquito with a tank.<br />
Anything more complex then one table with 5 columns or two tables will only going to make it harder to develop and not improve performance.<br />
You can forget about normalising and any fancy stuff you learn about databases in school. String field for a primary key would be as fast as integer when we talking about this size of data. So don't over-optimize with trying to have integers field that have state based on bitmask!<br />
<br />
Also remember that static (non dynamic) information about the game does not need to be stored in the database, that all include everything that does not change, i.e<br />
all token/card properties such as name, tooltips, "strength", color, etc. This is stored in material.inc.php and server has access to it from anywhere, as well as client<br />
if you send it with getAllDatas(). The only reason store some of it in database if it can affect your queries (i.e. type of token).<br />
<br />
Usually design process will contain the following steps:<br />
* Design game model - model that represent your game in progress, such as at any given step you can restore the game from that model<br />
* Mapping - now map real game to that model<br />
* Encoding - now represent this model in database and material file with reasonable amount of fields<br />
<br />
Example: '''The card game'''<br />
<br />
* In real word to "save" the game we take a picture a play area, save cards from it, then put away draw deck, discard and hand of each player separately and mark it, also we will record current scoring (if any) and who's turn was it<br />
* Framework handles state machine transition, so you don't have to worry about database design for that (i.e. who's turn it is, what phase of the game we are at, you still have to design it but as part of state machine step)<br />
* Also framework supports basic player information, color, order around the table, basic scoring, etc, so you don't have to worry about it either<br />
* The only thing you need in your database is state of the "board", which is "where each pieces is, and in what state", or (position,rotation) pair.<br />
* The card state is very simple, its usually "face up/face down", "tapped/untapped", "right side up/up side down"<br />
* As position go we never need real x,y,z. We need to know what "zone" card was, and depending on the zone it may sometimes need an extra "z" or "x" as card order. The zone position itself usually static or irrelevant.<br />
* So our model is: we have cards, which have some attributes, at any given point in time they belong to a "zone", and can also have order and state<br />
* Now for mapping we should consider what info changes and what info is static, static info is always candidate for material file or html<br />
* For dynamic stuff we should try to reduce amount of fields we need, i.e. we need a field for card, so its one, we need to know what zone cards belong to, its 2, and we have possible few other fields, but if you look closely at you game you may find out that most of the zone only need one attribute at a time, i.e. draw pile always have cards face down, hand always face up, also for hand and discard order does not matter at all (but for draw it does matter). So in majority of cases we can get away with one single extra integer field representing state or order<br />
* In real database both card and zone will be integers as primary keys referring to additional tables, but in our case its total overkill, so they can be strings as easily<br />
<br />
You can also use cards database schema and [[Deck]] implementation for most purposes (even you not dealing with cards).<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
Another Example: '''The euro game'''<br />
<br />
See details on database design for euro game at [[BGA_Studio_Cookbook#Database_for_The_euro_game]]<br />
<br />
<br />
So the piece mapping for non-grid based <br />
games can be in most case represented by (string: token_key, string: location, int: state), example of such database schema can be found here:<br />
[https://github.com/elaskavaia/bga-sharedcode/blob/master/dbmodel.sql dbmodel.sql] and class implementing access to it here [https://github.com/elaskavaia/bga-sharedcode/blob/master/modules/tokens.php tokens.php].<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `token` (<br />
`token_key` varchar(32) NOT NULL,<br />
`token_location` varchar(32) NOT NULL,<br />
`token_state` int(10),<br />
PRIMARY KEY (`token_key`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8;<br />
</pre><br />
<br />
<br />
See [[Game database model: dbmodel.sql]] for details about editing the file.<br />
<br />
Note: the simpler the database is the less debugging of db issues you have to deal with including database migration. The tokens database above - if you use it you never have to worry about migration because you don't need extra tables in 95% of the games.<br />
Here are some example of how real games are mapped to such database:<br />
<br />
'''Chess''' <br />
<br />
- chess is grid base game and normally you would use positional columns, but just for the sake of argument, the chess game will look like this<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|Q_white<br />
|f3<br />
|0<br />
|-<br />
|P_black_2<br />
|c6<br />
|0<br />
|-<br />
|K_black<br />
|e8<br />
|1<br />
|}<br />
And the state in this case indicated that kind was moved for example (which means castling cannot be performed)<br />
<br />
<br />
'''Classic card game''' <br />
<br />
Lets pretend we need 2 decks for that game<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|Q_spades_1<br />
|hand_ff0000<br />
|0 /* state not used for hand */<br />
|-<br />
|10_hearts_2<br />
|tableau_ff0000<br />
|2 /* position */<br />
|-<br />
|10_hearts_1<br />
|tableau_common<br />
|1 /* face down */<br />
|}<br />
<br />
'''Eminent Domain (card game)''' <br />
<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|card_tech_23<br />
|hand_ff0000<br />
|0 /* state not used for hand */<br />
|-<br />
|card_planet_19<br />
|tableau_ff0000<br />
|1 /* face up */<br />
|-<br />
|reource_s_22 /* silicon */<br />
|card_planet_19<br />
|2 /* production state */<br />
|-<br />
|fighter_F_1<br />
|tableau_ff0000<br />
|0<br />
|}<br />
<br />
<br />
You can also look at other games that use Tokens database and access layer: Nippon, Dungeon Petz, Lewis & Clark, Battleship, Russian Railroads, Khronos<br />
<br />
== Implement Game Setup ==<br />
<br />
Once you have your database schema you can do a proper game setup. Usually you open rulebook on the "Game Setup" page<br />
and implement these step by step populating the database (using db access API).<br />
Game initialization is performed in php method setupNewGame, this method is called once when game table is created.<br />
Game notifications cannot be sent during this time.<br />
<br />
== Implement One time game model synchronisation ==<br />
<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in UI, so we fix getAllDatas function<br />
to return all possible data we need to reconstruct the game. The template for getAllDatas already taking care of player info, but you <br />
have to alter it to return all other data from database visible to the "current" player.<br />
<br />
After that on the client side we should display this data, so in your .js file in setup function (which is the receiver of getAllDatas) you add calls that handle data send by server, usually by calling animation function such as "placeToken" or "placeCard".<br />
<br />
== Create State Machine ==<br />
<br />
Now you need to create a game state machine. <br />
<br />
The state handling spread across 4 files, so you have to make sure all the pieces are connected together.<br />
The state machine states.inc.php defines all the states, and function handlers on php side in a form of string,<br />
and if any of these functions are not implemented it would be very hard to debug because it will break in random places.<br />
<br />
Please first watch this again [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine BGA game state machine]<br />
and then please read [[Your game state machine: states.inc.php]].<br />
<br />
Now the state machine should be relatively simple. If you find yourself with machine with more than 20 states its probably not the way to go.<br />
Not all the player interactions need separate states, a lot of things can be implemented directly on client, i.e. if your player need to select<br />
a reward token, which offers choice of resource, instead of two states on server just have one state on server and possible few states on client (client side states)<br />
to collect this info.<br />
<br />
== Handle Turn Order ==<br />
<br />
If your game goes in clockwise order in natural sitting position nothing really needed you just use standard API and you are good. However if position is complecated<br />
it may require some trickery.<br />
<br />
Usually turn order is done by "game state" (see state machine above). Basically it would be two choices:<br />
* Turn order depends on game situation (such as we take player with highest number of red cubes)<br />
* Turn order is custom and assign on previos step - i.e. we not playing in clockwise order anymore. In this case you either need to extend player table with new order info (cannot! use player_no column) or use natural order markers (i.e. marker_ff0000 on position_1). In this we can buid player array in right order and pick next player based on previous player using existing helper function as $this->createNextPlayerTable<br />
<br />
== Implement Notification handling ==<br />
<br />
Now to implement things for real we have hook UI actions to ajax calls, and process notifications send by server.<br />
So previously we hooked onclick js handler right to client animation, in real game its a two<br />
step operation. When user clicks on something, client sends an ajax call to server, server processes it and updates database, server sends<br />
notification in response, client hooks animations to server notification. See [[Game_interface_logic:_yourgamename.js#Notifications|JS Notifications]].<br />
<br />
Exception to this is client states, if you need to process two step user interaction such as select meeple, place meeple, you may want <br />
to avoid sending data to server until step is complete (which may involve direct client side animation). See [[BGA_Studio_Cookbook#Multi_Step_Interactions:_Select_Worker.2FPlace_Worker_-_Using_Selection|Multi-Step Interactions]]<br />
<br />
Part of the sending notifications would be to update player's scoring, BGA uses standard control for score (on JS side), see [[Game_interface_logic:_yourgamename.js#Update_players_score|Update Player's Score]].<br />
<br />
In BGA there is only two ways interact with the server (officially)<br />
* Initial data dump - when JS client starts it gets all current data via setup() method<br />
* Game actions - ajaxcall from client, it returns error or ok (not data), then server send butch of notifications to client<br />
<br />
Note current ajaxcall is super vebosy and prone to errors, I suggest to use helper function. It does a lot of stuff you must do anyways.<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) args = []; // this allows to skip args parameter for action which do not require them<br />
<br />
args.lock = true; // this allows to avoid rapid action clicking which can cause race condition on server<br />
<br />
if (this.checkAction(action)) { // this does all the proper check that player is active and action is declared<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args, // this is mandatory fluff <br />
this, (result) => { }, // success result handler is empty - it is never needed<br />
handler); // this is real result handler - it called both on success and error, its is optional param - you rarely need it<br />
}<br />
},<br />
</pre><br />
<br />
When you insert a single action you have to update multiples files:<br />
* in ggg.js add ajaxcall, i.e. something like <br />
this.addActionButton('pass',_('Pass),()=>this.ajaxcallwrapper('pass'));<br />
* in states.php - add action 'pass' to list of possible actions<br />
'possibleactions' => ['pass','playCard']<br />
* in action.php - add action hander, see https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php<br />
* in game.php - add action hander, there you must do the following<br />
** call checkAction to validate the action<br />
** possible do more game specific check to validate what player doing is legal (even its not possible from your js side - player can cheat - not allow that)<br />
** do some database maniplations, using access api<br />
** send notifications - this is the "reply" for action<br />
** transition to new state (it very rare that user will remain in the same state, except for multi-active states)<br />
* back to ggg.js add notification subsciption and notification handler (two separate things)<br />
<br />
== Wrap Up ==<br />
<br />
<br />
* Implement game progression (getGameProgression() in php)<br />
* Implement Zombie turn (zombieTurn() in php)<br />
* Define and implemented some meaningful statistics for your game (i.e. total points, point from source A, B, C...)<br />
* The games logs should explain what happened if player was not looking<br />
* You need to implemented tiebreaking (using aux score field) and updated tiebreaker description in meta-data<br />
* Make sure all UI strings are marked for translation<br />
* UI elements which are images (i.e. tokens, cards) should have tooltips<br />
<br />
<br />
When you think you game is completely working there is still bunch of stuff you have to do/check before telling admin that game is ready, please go though this [[Pre-release checklist]].<br />
<br />
Finally, visit the game page for your alpha game (https://boardgamearena.com/gamepanel?game=…) to add the following information if you can:<br />
* Links to the rules (in multiple languages if available).<br />
* Links to teaching videos.<br />
* In the "On the web" section, links to:<br />
** The official website for the game (if there is one).<br />
** The BoardGameGeek page for the game.<br />
* Consider writing a summary of the rules.</div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Tutorial_reversi&diff=8353
Tutorial reversi
2021-05-26T04:02:44Z
<p>JonChambers: /* Setup the initial game position */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Reversi.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the [http://en.wikipedia.org/wiki/Reversi#Rules rules of Reversi].<br />
* Know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
<br />
== Create your first game ==<br />
<br />
With the initial skeleton of code provided initially, you can already start a game from the BGA Studio. For now, we are going to work with one player only. Most of the time it is simpler to proceed with only one player during the early phase of development of your game, as it's easy and fast to start/stop games.<br />
<br />
(If you choose to start with 2 players, you should see two names on the right: testdude0 and testdude1. To switch between them, press the red arrow button near their names; it will open another tab. This way you don't need to login and logout from multiple accounts.) <br />
<br />
Reminder: Always use the "Express Start" button to start the game. <br />
<br />
Thus, you can start a "Reversi" game, and arrive on a void, empty game. Yeah.<br />
<br />
== Let it look like Reversi ==<br />
<br />
It's always a good idea to start with a little bit of graphic work. Why? Because this helps to figure out how the final game will be, and issues that may appear later.<br />
<br />
Be careful designing the layout of your game: you must always keep in mind that players with a 1024px screen width must be able to play. Usually, it means that the width of the play area can be 750px (in the worst case).<br />
<br />
For Reversi, it's useless to have a 750x750px board - much too big, so we choose this one which fit perfectly (536x528):<br />
<br />
[[File:Board.jpg]]<br />
<br />
Note that we are using a jpg file. Jpg are lighter than png, so faster to load. Later we are going to use PNGs for discs for transparency purpose.<br />
<br />
Now, let's make it appear in our game:<br />
* upload board.jpg in your "img/" directory.<br />
* edit "reversi_reversi.tpl" to add a 'div' for your board.<br />
<br />
Note: If you are building this game by following the tutorial, you will have a different project name than 'reversi'. The file names in your project will be different than shown in this tutorial, replacing 'reversi' with your project name. It should be trivial to find the right file in your project, but be sure that any code (other than comments) that references 'reversi' is changed to your actual project name.<br />
<br />
<pre><br />
<div id="board"><br />
</div><br />
</pre><br />
<br />
* edit your reversi.css file to transform it into a visible board:<br />
<br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
}<br />
<br />
Refresh your page. Here's your board:<br />
<br />
[[File:reversi1.jpg]]<br />
<br />
== Make the squares appear ==<br />
<br />
Now, we need to create some invisible HTML elements where squares are. These elements will be used as position references for white and black discs. <br />
We need 64 squares. To avoid writing 64 'div' elements on our template, we are going to use the "block" feature.<br />
<br />
Let's modify our template like this:<br />
<br />
<pre><br />
<div id="board"><br />
<!-- BEGIN square --><br />
<div id="square_{X}_{Y}" class="square" style="left: {LEFT}px; top: {TOP}px;"></div><br />
<!-- END square --><br />
</div><br />
</pre><br />
<br />
As you can see, we created a "square" block, with 4 variable elements: X, Y, LEFT and TOP. We are going to use this block 64 times during page load.<br />
<br />
Let's do it in our "reversi.view.php" file:<br />
<br />
<pre><br />
$this->page->begin_block( "reversi_reversi", "square" );<br />
<br />
$hor_scale = 64.8;<br />
$ver_scale = 64.4;<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$this->page->insert_block( "square", array(<br />
'X' => $x,<br />
'Y' => $y,<br />
'LEFT' => round( ($x-1)*$hor_scale+10 ),<br />
'TOP' => round( ($y-1)*$ver_scale+7 )<br />
) );<br />
} <br />
}<br />
</pre><br />
<br />
Note: as you can see, squares in our "board.jpg" files do not have an exact width/height in pixels, and that's the reason we are using floating point numbers here.<br />
<br />
Now, to finish our work and check if everything works fine, we are going to style our square a little bit in our CSS stylesheet:<br />
<br />
<pre><br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
position: relative;<br />
}<br />
<br />
.square {<br />
width: 62px;<br />
height: 62px;<br />
position: absolute;<br />
background-color: red;<br />
}<br />
</pre><br />
<br />
Explanations:<br />
* With "position: relative" on board, we ensure square elements are positioned relatively to board.<br />
* For the test, we use a red background color for the square. This is a useful tip to figure out if everything is fine with invisible elements.<br />
<br />
Let's refresh and check our our (beautiful) squares:<br />
<br />
[[File:reversi2.jpg]]<br />
<br />
Hint: Now that we know our squares are set up correctly, we can hide the red background. You can remove the "background-color: red;" line from your .square class in the CSS stylesheet.<br />
<br />
== The discs ==<br />
<br />
Now, our board is ready to receive some disc tokens!<br />
<br />
[Note: Throughout this tutorial, sometimes "tokens" is used, and sometimes "discs" is used. They are often swapped if you're looking at code in the reversi example project.]<br />
<br />
At first, we introduce a new 'div' element as a child of "board" to host all these tokens (in our template):<br />
<br />
<pre><br />
<!-- END square --><br />
<br />
<div id="tokens"><br />
</div><br />
</div><br />
</pre><br />
<br />
Then, let's introduce a new piece of art with the discs. We need some transparency here so we are using a png file:<br />
<br />
[[File:tokens.png]]<br />
<br />
Upload this image file "tokens.png" in your "img/" directory.<br />
<br />
Important: we are using ONE file for both discs. It's really important that you use a minimum number of graphic files for your game with this "CSS sprite" technique, because it makes the game loading faster and more reliable. [http://www.w3schools.com/css/css_image_sprites.asp Read more about CSS sprites].<br />
<br />
Now, let's separate the disc with some CSS stuff:<br />
<br />
<pre><br />
.token {<br />
width: 56px;<br />
height: 56px;<br />
position: absolute;<br />
background-image: url('img/tokens.png');<br />
}<br />
.tokencolor_ffffff { background-position: 0px 0px; }<br />
.tokencolor_000000 { background-position: -56px 0px; }<br />
</pre><br />
<br />
With this CSS code, we apply the classes "token" and "tokencolor_ffffff" to a div element and we've got a white token. Yeah.<br />
<br />
Note the "position: absolute" which allows us to position tokens on the board and make them "slide" to their positions.<br />
<br />
Now, let's make a first token appear on our board. Disc tokens are not visible at the beginning of the game: they appear dynamically during the game. For this reason, we are going to make them appear from our Javascript code, with a BGA Framework technique called "JS template".<br />
<br />
In our template file (reversi_reversi.tpl), let's create the piece of HTML needed to display our token:<br />
<br />
<pre><br />
<script type="text/javascript"><br />
<br />
// Templates<br />
<br />
var jstpl_token='<div class="token tokencolor_${color}" id="token_${x_y}"></div>';<br />
<br />
</script><br />
</pre><br />
<br />
Note: we already created the "templates" section for you in the game skeleton.<br />
<br />
As you can see, we defined a JS template named "jstpl_token" with a piece of HTML and two variables: the color of the token and its x/y coordinates. Note that the syntax of the argument is different for template block variables (braces <code>{}</code>) and JS template variables (dollar and braces <code>${}</code>).<br />
<br />
Now, let's create a method in our Javascript code (in the "reversi.js" file) that will make a token appear on the board, using this template. Add under the section //// Utility methods:<br />
<br />
<pre><br />
addTokenOnBoard: function( x, y, player )<br />
{<br />
dojo.place( this.format_block( 'jstpl_token', {<br />
x_y: x+'_'+y,<br />
color: this.gamedatas.players[ player ].color<br />
} ) , 'tokens' );<br />
<br />
this.placeOnObject( 'token_'+x+'_'+y, 'overall_player_board_'+player );<br />
this.slideToObject( 'token_'+x+'_'+y, 'square_'+x+'_'+y ).play();<br />
},<br />
</pre><br />
<br />
At first, with "dojo.place" and "this.format_block" methods, we create a HTML piece of code and insert it as a new child of "tokens" div element.<br />
<br />
Then, with BGA "this.placeOnObject" method, we place this element over the panel of some player. Immediately after, using BGA "this.slideToObject" method, we make the disc slide to the "square" element, its final destination.<br />
<br />
"'overall_player_board_'+player" refers to the div element that contains each player's information and avatar. By initially placing the token here, it gives the effect that the player's avatar is throwing the token onto the board.<br />
<br />
Note: don't forget to call the "play()", otherwise the token remains at its original location.<br />
<br />
Note: note that during all the process, the parent of the new disc HTML element will remain "tokens". placeOnObject and slideToObject methods are only moving the position of elements on screen, and they are not modifying the HTML tree.<br />
<br />
Before we can show a token, we need to set the player colors in the setupNewGame function in reversi.game.php:<br />
<pre><br />
$default_colors = array( "ffffff", "000000" );<br />
</pre><br />
<br />
Note: Probably you'll have to remove the line "self::reattributeColorsBasedOnPreferences( $players, $gameinfos['player_colors'] );".<br />
<br />
<br />
Now, to test if everything works fine, just add "this.addTokenOnBoard( 2, 2, [player_id] )" in the "setup" Javascript method in reversi.js, and reload the page.<br />
<br />
<br />
A token should appear and slide immediately to its position, like this:<br />
<br />
[[File:reversi3.jpg]]<br />
<br />
== The database ==<br />
<br />
We did most of the client-side programming, so let's have a look on the other side now.<br />
<br />
To design the database model of our game, the best thing to do is to follow the "Go to game database" link at the bottom of our game, to access the database directly with a [http://www.phpmyadmin.net/ PhpMyAdmin] instance. Your PhpMyAdmin username/password is in your welcome email (and currently the same as the SFTP username/password).<br />
<br />
Then, you can create the tables you need for your table (do not remove existing tables!), and report every SQL command used in your "dbmodel.sql" file. How do you generate SQL to create table after creating the table in the UI? See [https://www.itsupportguides.com/knowledge-base/tech-tips-tricks/how-to-generate-sql-create-table-script-using-phpmyadmin/ here]. Add 'IF NOT EXISTS' to the CREATE TABLE sql (see example below).<br />
<br />
[[File:reversi4.jpg]]<br />
<br />
The database model of Reversi is very simple: just one table with the squares of the board. <br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `board` (<br />
`board_x` smallint(5) unsigned NOT NULL,<br />
`board_y` smallint(5) unsigned NOT NULL,<br />
`board_player` int(10) unsigned DEFAULT NULL,<br />
PRIMARY KEY (`board_x`,`board_y`)<br />
) ENGINE=InnoDB;<br />
</pre><br />
<br />
Add the above SQL to dbmodel.sql. Now, a new database with a "board" table will be created each time we start a Reversi game. This is why after modifying our dbmodel.sql it's a good time to stop & start again our game.<br />
<br />
== Setup the initial game position ==<br />
<br />
The "setupNewGame" method of our reversi.game.php is called during initial setup: this is the place to initialize our data and to place the initial tokens on the board (initially, there are 4 tokens on the board).<br />
<br />
Let's do this:<br />
<br />
<pre><br />
// Init the board<br />
$sql = "INSERT INTO board (board_x,board_y,board_player) VALUES ";<br />
$sql_values = array();<br />
list( $blackplayer_id, $whiteplayer_id ) = array_keys( $players );<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$token_value = "NULL";<br />
if( ($x==4 && $y==4) || ($x==5 && $y==5) ) // Initial positions of white player<br />
$token_value = "'$whiteplayer_id'";<br />
else if( ($x==4 && $y==5) || ($x==5 && $y==4) ) // Initial positions of black player<br />
$token_value = "'$blackplayer_id'";<br />
<br />
$sql_values[] = "('$x','$y',$token_value)";<br />
}<br />
}<br />
$sql .= implode( ',', $sql_values );<br />
self::DbQuery( $sql );<br />
<br />
<br />
// Active first player<br />
self::activeNextPlayer(); <br />
</pre><br />
<br />
We create one table entry for each square, with a "NULL" value which means "empty square". For 4 specific squares, we place an initial token.<br />
<br />
At the end, we call activeNextPlayer to make the first player active at the beginning of the game.<br />
<br />
You need to remove the call to self::reattributeColorsBasedOnPreferences() in SetupNewGame() so that any player color preferences do not override the two colors supported here.<br />
<br />
Now, we need to make these tokens appear on the client side. The first step is to return the token positions with our "getAllDatas" PHP method (called during each page reload):<br />
<br />
<pre><br />
// Get reversi board token<br />
$result['board'] = self::getObjectListFromDB( "SELECT board_x x, board_y y, board_player player<br />
FROM board<br />
WHERE board_player IS NOT NULL" );<br />
</pre><br />
<br />
We are using the BGA framework "getObjectListFromDB" method that formats the result of this SQL query in a PHP array with x, y and player attributes.<br />
<br />
Last, we process this array client side, and place a disc token on the board for each array item. We do this using our Javascript "setup" method:<br />
<br />
<pre><br />
for( var i in gamedatas.board )<br />
{<br />
var square = gamedatas.board[i];<br />
<br />
if( square.player !== null )<br />
{<br />
this.addTokenOnBoard( square.x, square.y, square.player );<br />
}<br />
}<br />
</pre><br />
<br />
Our "board" entry created in "getAllDatas" can be used here as "gamedatas.board" in our Javascript. We are using our previously developed "addTokenOnBoard" method.<br />
<br />
Reload... and here we are:<br />
<br />
[[File:reversi5.jpg]]<br />
<br />
It starts to smell Reversi here...<br />
<br />
== The game state machine ==<br />
<br />
Now, let's stop our game again, because we are going to start the core game logic.<br />
<br />
You already read [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine], so you know that this is the heart of your game logic. For reversi, it's very simple. Here's a diagram of our game state machine:<br />
<br />
[[File:reversi6.jpg]]<br />
<br />
And here's our "states.inc.php", according to this diagram:<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 10 )<br />
),<br />
<br />
10 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a disc'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a disc'),<br />
"type" => "activeplayer",<br />
"args" => "argPlayerTurn",<br />
"possibleactions" => array( 'playDisc' ),<br />
"transitions" => array( "playDisc" => 11, "zombiePass" => 11 )<br />
),<br />
<br />
11 => array(<br />
"name" => "nextPlayer",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "nextTurn" => 10, "cantPlay" => 11, "endGame" => 99 )<br />
),<br />
<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
Now, let's create in reversi.game.php the methods that are declared in this game states description file:<br />
* argPlayerTurn<br />
* stNextPlayer<br />
<br />
... and start a new Reversi game.<br />
<br />
As you can see on the screen capture below, the BGA framework makes the game jump to our first game state "playerTurn" right after the initial setup. That's why the status bar contains the description of playerTurn state ("XXXX must play a disc"):<br />
<br />
[[File:reversi7.jpg]]<br />
<br />
== The rules ==<br />
<br />
We will use the "getPossibleMoves" PHP method to:<br />
* Indicate to the current player where she is allowed to play by returning a list of coordinates<br />
* Check if the player has the right to play in the spot they choose<br />
<br />
<br />
This is pure PHP programming here, and there are no special things from the BGA framework that can be used. This is why we won't go into details here. The overall idea is:<br />
* Create a "getTurnedOverDiscs(x,y)" method that returns coordinates of discs that would be turned over if a token would be played at x,y.<br />
* Loop through all free squares of the board and call the "getTurnedOverDiscs" method on each of them. If at least 1 token is turned over, this is a valid move.<br />
<br />
IMPORTANT: Keep in mind that making a database query is slow, so please don't load the entire game board with a SQL query multiple times. In our implementation, we load the entire board once at the beginning of "getPossibleMoves", and then pass the board as an argument to all methods.<br />
<br />
If you want to look into details, please look at the "utility method" sections of reversi.game.php. If building the tutorial yourself, copy the functions under "Utility functions" comment from the Reversi tutorial.<br />
<br />
== Display allowed moves ==<br />
<br />
Now we want to highlight the squares where the player can place a disc.<br />
<br />
To do this, we add a "argPlayerTurn" method in reversi.game.php. This method is called on the server each time we enter into "playerTurn" game state, and its result is transferred automatically to the client-side:<br />
<br />
<pre><br />
function argPlayerTurn()<br />
{<br />
return array(<br />
'possibleMoves' => self::getPossibleMoves( self::getActivePlayerId() )<br />
);<br />
}<br />
</pre><br />
<br />
We use the "getPossibleMoves" method we just developed.<br />
<br />
Each time we enter into a new game state, we use the "onEnteringState" Javascript method (in the reversi.js file, under "Game & client states"). This lets us use the data returned by the method above on the client side.<br />
<br />
<pre><br />
onEnteringState: function( stateName, args )<br />
{<br />
console.log( 'Entering state: '+stateName );<br />
<br />
switch( stateName )<br />
{<br />
case 'playerTurn':<br />
this.updatePossibleMoves( args.args.possibleMoves );<br />
break;<br />
}<br />
},<br />
</pre><br />
<br />
So, when we enter into "playerTurn" game state, we call our "updatePossibleMoves" method (under the "Utility functions" section). This method looks like this:<br />
<br />
<pre><br />
updatePossibleMoves: function( possibleMoves )<br />
{<br />
// Remove current possible moves<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
<br />
for( var x in possibleMoves )<br />
{<br />
for( var y in possibleMoves[ x ] )<br />
{<br />
// x,y is a possible move<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
} <br />
}<br />
<br />
this.addTooltipToClass( 'possibleMove', '', _('Place a disc here') );<br />
},<br />
</pre><br />
Here's what this does. At first, it removes all "possibleMove" classes currently applied with the very useful combination of "dojo.query" and "removeClass" method.<br />
<br />
Then it loops through all possible moves our PHP "updatePossibleMoves" function created for us, and adds the "possibleMove" class to each corresponding square.<br />
<br />
Finally, it uses the BGA framework "addTooltipToClass" method to associate a tooltip to all those highlighted squares so that players can understand their meaning.<br />
<br />
To see the possible moves we need to create a CSS class ("possibleMove") that can be applied to a "square" element to highlight it:<br />
<pre><br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
</pre><br />
<br />
<br />
<br />
And here we are:<br />
<br />
[[File:reversi8.jpg.jpg]]<br />
<br />
== Let's play ==<br />
<br />
From now, it's better to restart a game with 2 players, because we are going to implement a complete Reversi turn. The summary of what we are going to do is:<br />
* When we click on a "possibleMove" square, send the move to the server.<br />
* Server side, check the move is correct, apply Reversi rules and jump to next player.<br />
* Client side, change the disc position to reflect the move.<br />
<br />
First we associate each click on a square to one of our methods using our Javascript "setup" method:<br />
<br />
<pre><br />
dojo.query( '.square' ).connect( 'onclick', this, 'onPlayDisc' );<br />
</pre><br />
<br />
Note the use of the "dojo.query" method to get all HTML elements with "square" class in just one function call. Now, our "onPlayDisc" method is called each time someone clicks on a square.<br />
<br />
Here's our "onPlayDisc" method below:<br />
<br />
<pre><br />
onPlayDisc: function( evt )<br />
{<br />
// Stop this event propagation<br />
dojo.stopEvent( evt );<br />
<br />
// Get the cliqued square x and y<br />
// Note: square id format is "square_X_Y"<br />
var coords = evt.currentTarget.id.split('_');<br />
var x = coords[1];<br />
var y = coords[2];<br />
<br />
if( ! dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{<br />
// This is not a possible move => the click does nothing<br />
return ;<br />
}<br />
<br />
if( this.checkAction( 'playDisc' ) ) // Check that this action is possible at this moment<br />
{ <br />
this.ajaxcall( "/reversi/reversi/playDisc.html", {<br />
x:x,<br />
y:y<br />
}, this, function( result ) {} );<br />
} <br />
},<br />
</pre><br />
<br />
What we do here is:<br />
* We stop the propagation of the Javascript "onclick" event. Otherwise, it can lead to random behavior so it's always a good idea.<br />
* We get the x/y coordinates of the square by using "evt.currentTarget.id".<br />
* We check that clicked square has the "possibleMove" class, otherwise we know for sure that we can't play there.<br />
* We check that "playDisc" action is possible, according to current game state (see "possibleactions" entry in our "playerTurn" game state defined above). This check is important to avoid issues if a player double clicks on a square.<br />
* Finally, we make a call to the server using BGA "ajaxcall" method with argument x and y. Be sure to update the first parameter to match your game if building the tutorial yourself. E.g. "/yourgamename/yourgamename/playDisc.html"<br />
<br />
Now, we have to manage this "playDisc" action on the server side. At first, we introduce a "playDisc" entry point in our "reversi.action.php":<br />
<br />
<pre><br />
public function playDisc()<br />
{<br />
self::setAjaxMode(); <br />
$x = self::getArg( "x", AT_posint, true );<br />
$y = self::getArg( "y", AT_posint, true );<br />
$result = $this->game->playDisc( $x, $y );<br />
self::ajaxResponse( );<br />
}<br />
</pre><br />
<br />
As you can see, we get the 2 arguments x and y from the javascript call, and call a corresponding "playDisc" method in our game logic (reversi.game.php).<br />
<br />
Now, let's have a look of this playDisc method:<br />
<br />
<pre><br />
function playDisc( $x, $y )<br />
{<br />
// Check that this player is active and that this action is possible at this moment<br />
self::checkAction( 'playDisc' ); <br />
</pre><br />
<br />
... at first, we check that this action is possible according to current game state (see "possible action"). We already did it on client side, but it's important to do it on server side too (otherwise it would be possible to cheat).<br />
<br />
<pre><br />
// Now, check if this is a possible move<br />
$board = self::getBoard();<br />
$player_id = self::getActivePlayerId();<br />
$turnedOverDiscs = self::getTurnedOverDiscs( $x, $y, $player_id, $board );<br />
<br />
if( count( $turnedOverDiscs ) > 0 )<br />
{<br />
// This move is possible!<br />
</pre><br />
<br />
...now, we are using the "getTurnedOverDiscs" method again to check that this move is possible.<br />
<br />
<pre><br />
// Let's place a disc at x,y and return all "$returned" discs to the active player<br />
<br />
$sql = "UPDATE board SET board_player='$player_id'<br />
WHERE ( board_x, board_y) IN ( ";<br />
<br />
foreach( $turnedOverDiscs as $turnedOver )<br />
{<br />
$sql .= "('".$turnedOver['x']."','".$turnedOver['y']."'),";<br />
}<br />
$sql .= "('$x','$y') ) ";<br />
<br />
self::DbQuery( $sql );<br />
<br />
</pre><br />
<br />
... we update the database to change the color of all turned over disc + the disc we just placed.<br />
<br />
<pre><br />
// Update scores according to the number of disc on board<br />
$sql = "UPDATE player<br />
SET player_score = (<br />
SELECT COUNT( board_x ) FROM board WHERE board_player=player_id<br />
)";<br />
self::DbQuery( $sql );<br />
<br />
// Statistics<br />
self::incStat( count( $turnedOverDiscs ), "turnedOver", $player_id );<br />
if( ($x==1 && $y==1) || ($x==8 && $y==1) || ($x==1 && $y==8) || ($x==8 && $y==8) )<br />
self::incStat( 1, 'discPlayedOnCorner', $player_id );<br />
else if( $x==1 || $x==8 || $y==1 || $y==8 )<br />
self::incStat( 1, 'discPlayedOnBorder', $player_id );<br />
else if( $x>=3 && $x<=6 && $y>=3 && $y<=6 )<br />
self::incStat( 1, 'discPlayedOnCenter', $player_id );<br />
<br />
</pre><br />
<br />
... now, we update both player score by counting all disc, and we manage game statistics.<br />
<br />
<pre><br />
// Notify<br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ), array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
<br />
self::notifyAllPlayers( "turnOverDiscs", '', array(<br />
'player_id' => $player_id,<br />
'turnedOver' => $turnedOverDiscs<br />
) );<br />
<br />
$newScores = self::getCollectionFromDb( "SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", "", array(<br />
"scores" => $newScores<br />
) );<br />
</pre><br />
<br />
... then we notify about all these changes. We are using for that 3 notifications ('playDisc', 'turnOverDiscs' and 'newScores' that we are going to implement on client side later). Note that the description of the 'playDisc' notification will be logged in the game log.<br />
<br />
<pre><br />
// Then, go to the next state<br />
$this->gamestate->nextState( 'playDisc' );<br />
}<br />
else<br />
throw new feException( "Impossible move" );<br />
}<br />
</pre><br />
<br />
... finally, we jump to the next game state if everything goes fine ('playDisc' is the name of a transition in the 'playerTurn' game state description above which leads to state 11 which is 'nextPlayer').<br />
<br />
To make the statistics work, we have to initialize them in stats.inc.php:<br />
<br />
<pre><br />
// Statistics existing for each player<br />
"player" => array(<br />
<br />
"discPlayedOnCorner" => array( "id"=> 10,<br />
"name" => totranslate("Discs played on a corner"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnBorder" => array( "id"=> 11,<br />
"name" => totranslate("Discs played on a border"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnCenter" => array( "id"=> 12,<br />
"name" => totranslate("Discs played on board center part"), <br />
"type" => "int" ),<br />
<br />
"turnedOver" => array( "id"=> 13,<br />
"name" => totranslate("Number of discs turned over"), <br />
"type" => "int" ) <br />
)<br />
</pre><br />
<br />
A last thing to do on the server side is to activate the next player when we enter the "nextPlayer" game state (in the "reversi.game.php" file, under "Game state reactions"):<br />
<br />
<pre><br />
function stNextPlayer()<br />
{<br />
// Active next player<br />
$player_id = self::activeNextPlayer();<br />
<br />
// Check if both player has at least 1 discs, and if there are free squares to play<br />
$player_to_discs = self::getCollectionFromDb( "SELECT board_player, COUNT( board_x )<br />
FROM board<br />
GROUP BY board_player", true );<br />
<br />
if( ! isset( $player_to_discs[ null ] ) )<br />
{<br />
// Index 0 has not been set => there's no more free place on the board !<br />
// => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
else if( ! isset( $player_to_discs[ $player_id ] ) )<br />
{<br />
// Active player has no more disc on the board => he looses immediately<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
<br />
// Can this player play?<br />
<br />
$possibleMoves = self::getPossibleMoves( $player_id );<br />
if( count( $possibleMoves ) == 0 )<br />
{<br />
<br />
// This player can't play<br />
// Can his opponent play ?<br />
$opponent_id = self::getUniqueValueFromDb( "SELECT player_id FROM player WHERE player_id!='$player_id' " );<br />
if( count( self::getPossibleMoves( $opponent_id ) ) == 0 )<br />
{<br />
// Nobody can move => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
}<br />
else<br />
{ <br />
// => pass his turn<br />
$this->gamestate->nextState( 'cantPlay' );<br />
}<br />
}<br />
else<br />
{<br />
// This player can play. Give him some extra time<br />
self::giveExtraTime( $player_id );<br />
$this->gamestate->nextState( 'nextTurn' );<br />
}<br />
<br />
}<br />
</pre><br />
<br />
Now, when we play a disc, the rules are checked and the disc appears in the database.<br />
<br />
[[File:reversi9.jpg]]<br />
<br />
Of course, as we don't manage notifications on client side, we need to press F5 after each move to see the changes on the board.<br />
<br />
== Make the move appear automatically ==<br />
<br />
Now, what we have to do is process the notifications sent by the server and make the move appear on the interface.<br />
<br />
In our "setupNotifications" method, we register 2 methods for the 2 notifications we created at the previous step ('playDisc' and 'turnOverDiscs'):<br />
<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 );<br />
dojo.subscribe( 'turnOverDiscs', this, "notif_turnOverDiscs" );<br />
this.notifqueue.setSynchronous( 'turnOverDiscs', 1500 );<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
this.notifqueue.setSynchronous( 'newScores', 500 );<br />
</pre><br />
<br />
As you can see, we associate each of our 3 notifications with a method prefixed with "notif_". We also define these notifications as "synchronous", with a duration in millisecond. It tells the user interface to wait some time after executing the notification, to let the animation end before starting the next notification. In our specific case, the animation will be the following:<br />
* Make a disc slide from the player panel to its place on the board<br />
* (wait 500ms)<br />
* Make all turned over discs blink (and of course turned them over)<br />
* (wait 1500ms)<br />
* Update the player scores<br />
* (wait 500ms)<br />
<br />
The 2nd parameter in dodo.subscribe call (this) is the 'context', and will be passed in as a parameter to the specified method.<br />
<br />
Let's have a look now on the "playDisc" notification handler method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addTokenOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
No surprise here, we re-used some existing stuff to:<br />
* Remove the highlighted squares.<br />
* Add a new disc on board, coming from player panel.<br />
<br />
Now, here's the method that handles the turnOverDiscs notification:<br />
<br />
<pre><br />
notif_turnOverDiscs: function( notif )<br />
{<br />
// Get the color of the player who is returning the discs<br />
var targetColor = this.gamedatas.players[ notif.args.player_id ].color;<br />
<br />
// Make these discs blink and set them to the specified color<br />
for( var i in notif.args.turnedOver )<br />
{<br />
var token = notif.args.turnedOver[ i ];<br />
<br />
// Make the token blink 2 times<br />
var anim = dojo.fx.chain( [<br />
dojo.fadeOut( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeOut( { <br />
node: 'token_'+token.x+'_'+token.y,<br />
onEnd: function( node ) {<br />
<br />
// Remove any color class<br />
dojo.removeClass( node, [ 'tokencolor_000000', 'tokencolor_ffffff' ] );<br />
// ... and add the good one<br />
dojo.addClass( node, 'tokencolor_'+targetColor );<br />
<br />
} <br />
} ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } )<br />
<br />
] ); // end of dojo.fx.chain<br />
<br />
// ... and launch the animation<br />
anim.play(); <br />
}<br />
},<br />
</pre><br />
<br />
The list of the discs to be turned over has been made available by our server side code in "notif.args.turnedOver" (see previous paragraph). We loop through all these discs, and create a complex animation using dojo.Animation for each of them. The complete documentation on dojo animations [http://dojotoolkit.org/documentation/tutorials/1.6/animation/ can be found here].<br />
<br />
In few words: we create a chain of 4 animations to make the disc fade out, fade in, fade out again, and fade in again. At the end of the second fade out, we change the color of the disc. Finally, we launch the animation with "play()".<br />
<br />
And Also the notification to update the scores:<br />
<br />
<pre><br />
notif_newScores: function( notif )<br />
{<br />
for( var player_id in notif.args.scores )<br />
{<br />
var newScore = notif.args.scores[ player_id ];<br />
this.scoreCtrl[ player_id ].toValue( newScore );<br />
}<br />
},<br />
</pre></div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Tutorial_reversi&diff=8351
Tutorial reversi
2021-05-26T02:04:01Z
<p>JonChambers: /* The discs */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Reversi.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the [http://en.wikipedia.org/wiki/Reversi#Rules rules of Reversi].<br />
* Know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
<br />
== Create your first game ==<br />
<br />
With the initial skeleton of code provided initially, you can already start a game from the BGA Studio. For now, we are going to work with one player only. Most of the time it is simpler to proceed with only one player during the early phase of development of your game, as it's easy and fast to start/stop games.<br />
<br />
(If you choose to start with 2 players, you should see two names on the right: testdude0 and testdude1. To switch between them, press the red arrow button near their names; it will open another tab. This way you don't need to login and logout from multiple accounts.) <br />
<br />
Reminder: Always use the "Express Start" button to start the game. <br />
<br />
Thus, you can start a "Reversi" game, and arrive on a void, empty game. Yeah.<br />
<br />
== Let it look like Reversi ==<br />
<br />
It's always a good idea to start with a little bit of graphic work. Why? Because this helps to figure out how the final game will be, and issues that may appear later.<br />
<br />
Be careful designing the layout of your game: you must always keep in mind that players with a 1024px screen width must be able to play. Usually, it means that the width of the play area can be 750px (in the worst case).<br />
<br />
For Reversi, it's useless to have a 750x750px board - much too big, so we choose this one which fit perfectly (536x528):<br />
<br />
[[File:Board.jpg]]<br />
<br />
Note that we are using a jpg file. Jpg are lighter than png, so faster to load. Later we are going to use PNGs for discs for transparency purpose.<br />
<br />
Now, let's make it appear in our game:<br />
* upload board.jpg in your "img/" directory.<br />
* edit "reversi_reversi.tpl" to add a 'div' for your board.<br />
<br />
Note: If you are building this game by following the tutorial, you will have a different project name than 'reversi'. The file names in your project will be different than shown in this tutorial, replacing 'reversi' with your project name. It should be trivial to find the right file in your project, but be sure that any code (other than comments) that references 'reversi' is changed to your actual project name.<br />
<br />
<pre><br />
<div id="board"><br />
</div><br />
</pre><br />
<br />
* edit your reversi.css file to transform it into a visible board:<br />
<br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
}<br />
<br />
Refresh your page. Here's your board:<br />
<br />
[[File:reversi1.jpg]]<br />
<br />
== Make the squares appear ==<br />
<br />
Now, we need to create some invisible HTML elements where squares are. These elements will be used as position references for white and black discs. <br />
We need 64 squares. To avoid writing 64 'div' elements on our template, we are going to use the "block" feature.<br />
<br />
Let's modify our template like this:<br />
<br />
<pre><br />
<div id="board"><br />
<!-- BEGIN square --><br />
<div id="square_{X}_{Y}" class="square" style="left: {LEFT}px; top: {TOP}px;"></div><br />
<!-- END square --><br />
</div><br />
</pre><br />
<br />
As you can see, we created a "square" block, with 4 variable elements: X, Y, LEFT and TOP. We are going to use this block 64 times during page load.<br />
<br />
Let's do it in our "reversi.view.php" file:<br />
<br />
<pre><br />
$this->page->begin_block( "reversi_reversi", "square" );<br />
<br />
$hor_scale = 64.8;<br />
$ver_scale = 64.4;<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$this->page->insert_block( "square", array(<br />
'X' => $x,<br />
'Y' => $y,<br />
'LEFT' => round( ($x-1)*$hor_scale+10 ),<br />
'TOP' => round( ($y-1)*$ver_scale+7 )<br />
) );<br />
} <br />
}<br />
</pre><br />
<br />
Note: as you can see, squares in our "board.jpg" files do not have an exact width/height in pixels, and that's the reason we are using floating point numbers here.<br />
<br />
Now, to finish our work and check if everything works fine, we are going to style our square a little bit in our CSS stylesheet:<br />
<br />
<pre><br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
position: relative;<br />
}<br />
<br />
.square {<br />
width: 62px;<br />
height: 62px;<br />
position: absolute;<br />
background-color: red;<br />
}<br />
</pre><br />
<br />
Explanations:<br />
* With "position: relative" on board, we ensure square elements are positioned relatively to board.<br />
* For the test, we use a red background color for the square. This is a useful tip to figure out if everything is fine with invisible elements.<br />
<br />
Let's refresh and check our our (beautiful) squares:<br />
<br />
[[File:reversi2.jpg]]<br />
<br />
Hint: Now that we know our squares are set up correctly, we can hide the red background. You can remove the "background-color: red;" line from your .square class in the CSS stylesheet.<br />
<br />
== The discs ==<br />
<br />
Now, our board is ready to receive some disc tokens!<br />
<br />
[Note: Throughout this tutorial, sometimes "tokens" is used, and sometimes "discs" is used. They are often swapped if you're looking at code in the reversi example project.]<br />
<br />
At first, we introduce a new 'div' element as a child of "board" to host all these tokens (in our template):<br />
<br />
<pre><br />
<!-- END square --><br />
<br />
<div id="tokens"><br />
</div><br />
</div><br />
</pre><br />
<br />
Then, let's introduce a new piece of art with the discs. We need some transparency here so we are using a png file:<br />
<br />
[[File:tokens.png]]<br />
<br />
Upload this image file "tokens.png" in your "img/" directory.<br />
<br />
Important: we are using ONE file for both discs. It's really important that you use a minimum number of graphic files for your game with this "CSS sprite" technique, because it makes the game loading faster and more reliable. [http://www.w3schools.com/css/css_image_sprites.asp Read more about CSS sprites].<br />
<br />
Now, let's separate the disc with some CSS stuff:<br />
<br />
<pre><br />
.token {<br />
width: 56px;<br />
height: 56px;<br />
position: absolute;<br />
background-image: url('img/tokens.png');<br />
}<br />
.tokencolor_ffffff { background-position: 0px 0px; }<br />
.tokencolor_000000 { background-position: -56px 0px; }<br />
</pre><br />
<br />
With this CSS code, we apply the classes "token" and "tokencolor_ffffff" to a div element and we've got a white token. Yeah.<br />
<br />
Note the "position: absolute" which allows us to position tokens on the board and make them "slide" to their positions.<br />
<br />
Now, let's make a first token appear on our board. Disc tokens are not visible at the beginning of the game: they appear dynamically during the game. For this reason, we are going to make them appear from our Javascript code, with a BGA Framework technique called "JS template".<br />
<br />
In our template file (reversi_reversi.tpl), let's create the piece of HTML needed to display our token:<br />
<br />
<pre><br />
<script type="text/javascript"><br />
<br />
// Templates<br />
<br />
var jstpl_token='<div class="token tokencolor_${color}" id="token_${x_y}"></div>';<br />
<br />
</script><br />
</pre><br />
<br />
Note: we already created the "templates" section for you in the game skeleton.<br />
<br />
As you can see, we defined a JS template named "jstpl_token" with a piece of HTML and two variables: the color of the token and its x/y coordinates. Note that the syntax of the argument is different for template block variables (braces <code>{}</code>) and JS template variables (dollar and braces <code>${}</code>).<br />
<br />
Now, let's create a method in our Javascript code (in the "reversi.js" file) that will make a token appear on the board, using this template. Add under the section //// Utility methods:<br />
<br />
<pre><br />
addTokenOnBoard: function( x, y, player )<br />
{<br />
dojo.place( this.format_block( 'jstpl_token', {<br />
x_y: x+'_'+y,<br />
color: this.gamedatas.players[ player ].color<br />
} ) , 'tokens' );<br />
<br />
this.placeOnObject( 'token_'+x+'_'+y, 'overall_player_board_'+player );<br />
this.slideToObject( 'token_'+x+'_'+y, 'square_'+x+'_'+y ).play();<br />
},<br />
</pre><br />
<br />
At first, with "dojo.place" and "this.format_block" methods, we create a HTML piece of code and insert it as a new child of "tokens" div element.<br />
<br />
Then, with BGA "this.placeOnObject" method, we place this element over the panel of some player. Immediately after, using BGA "this.slideToObject" method, we make the disc slide to the "square" element, its final destination.<br />
<br />
"'overall_player_board_'+player" refers to the div element that contains each player's information and avatar. By initially placing the token here, it gives the effect that the player's avatar is throwing the token onto the board.<br />
<br />
Note: don't forget to call the "play()", otherwise the token remains at its original location.<br />
<br />
Note: note that during all the process, the parent of the new disc HTML element will remain "tokens". placeOnObject and slideToObject methods are only moving the position of elements on screen, and they are not modifying the HTML tree.<br />
<br />
Before we can show a token, we need to set the player colors in the setupNewGame function in reversi.game.php:<br />
<pre><br />
$default_colors = array( "ffffff", "000000" );<br />
</pre><br />
<br />
Note: Probably you'll have to remove the line "self::reattributeColorsBasedOnPreferences( $players, $gameinfos['player_colors'] );".<br />
<br />
<br />
Now, to test if everything works fine, just add "this.addTokenOnBoard( 2, 2, [player_id] )" in the "setup" Javascript method in reversi.js, and reload the page.<br />
<br />
<br />
A token should appear and slide immediately to its position, like this:<br />
<br />
[[File:reversi3.jpg]]<br />
<br />
== The database ==<br />
<br />
We did most of the client-side programming, so let's have a look on the other side now.<br />
<br />
To design the database model of our game, the best thing to do is to follow the "Go to game database" link at the bottom of our game, to access the database directly with a [http://www.phpmyadmin.net/ PhpMyAdmin] instance. Your PhpMyAdmin username/password is in your welcome email (and currently the same as the SFTP username/password).<br />
<br />
Then, you can create the tables you need for your table (do not remove existing tables!), and report every SQL command used in your "dbmodel.sql" file. How do you generate SQL to create table after creating the table in the UI? See [https://www.itsupportguides.com/knowledge-base/tech-tips-tricks/how-to-generate-sql-create-table-script-using-phpmyadmin/ here]. Add 'IF NOT EXISTS' to the CREATE TABLE sql (see example below).<br />
<br />
[[File:reversi4.jpg]]<br />
<br />
The database model of Reversi is very simple: just one table with the squares of the board. <br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `board` (<br />
`board_x` smallint(5) unsigned NOT NULL,<br />
`board_y` smallint(5) unsigned NOT NULL,<br />
`board_player` int(10) unsigned DEFAULT NULL,<br />
PRIMARY KEY (`board_x`,`board_y`)<br />
) ENGINE=InnoDB;<br />
</pre><br />
<br />
Add the above SQL to dbmodel.sql. Now, a new database with a "board" table will be created each time we start a Reversi game. This is why after modifying our dbmodel.sql it's a good time to stop & start again our game.<br />
<br />
== Setup the initial game position ==<br />
<br />
The "setupNewGame" method of our reversi.game.php is called during initial setup: this is the place to initialize our data and to place the initial tokens on the board (initially, there are 4 tokens on the board).<br />
<br />
Let's do this:<br />
<br />
<pre><br />
// Init the board<br />
$sql = "INSERT INTO board (board_x,board_y,board_player) VALUES ";<br />
$sql_values = array();<br />
list( $blackplayer_id, $whiteplayer_id ) = array_keys( $players );<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$token_value = "NULL";<br />
if( ($x==4 && $y==4) || ($x==5 && $y==5) ) // Initial positions of white player<br />
$token_value = "'$whiteplayer_id'";<br />
else if( ($x==4 && $y==5) || ($x==5 && $y==4) ) // Initial positions of black player<br />
$token_value = "'$blackplayer_id'";<br />
<br />
$sql_values[] = "('$x','$y',$token_value)";<br />
}<br />
}<br />
$sql .= implode( ',', $sql_values );<br />
self::DbQuery( $sql );<br />
<br />
<br />
// Active first player<br />
self::activeNextPlayer(); <br />
</pre><br />
<br />
We create one table entry for each square, with a "NULL" value which means "empty square". For 4 specific squares, we place an initial token.<br />
<br />
At the end, we call activeNextPlayer to make the first player active at the beginning of the game.<br />
<br />
You may need to remove the call to self::reattributeColorsBasedOnPreferences() in SetupNewGame() so that any player color preferences do not override the two colors supported here.<br />
<br />
Now, we need to make these tokens appear on the client side. The first step is to return the token positions with our "getAllDatas" PHP method (called during each page reload):<br />
<br />
<pre><br />
// Get reversi board token<br />
$result['board'] = self::getObjectListFromDB( "SELECT board_x x, board_y y, board_player player<br />
FROM board<br />
WHERE board_player IS NOT NULL" );<br />
</pre><br />
<br />
We are using the BGA framework "getObjectListFromDB" method that formats the result of this SQL query in a PHP array with x, y and player attributes.<br />
<br />
Last, we process this array client side, and place a disc token on the board for each array item. We do this using our Javascript "setup" method:<br />
<br />
<pre><br />
for( var i in gamedatas.board )<br />
{<br />
var square = gamedatas.board[i];<br />
<br />
if( square.player !== null )<br />
{<br />
this.addTokenOnBoard( square.x, square.y, square.player );<br />
}<br />
}<br />
</pre><br />
<br />
Our "board" entry created in "getAllDatas" can be used here as "gamedatas.board" in our Javascript. We are using our previously developed "addTokenOnBoard" method.<br />
<br />
Reload... and here we are:<br />
<br />
[[File:reversi5.jpg]]<br />
<br />
It starts to smell Reversi here...<br />
<br />
== The game state machine ==<br />
<br />
Now, let's stop our game again, because we are going to start the core game logic.<br />
<br />
You already read [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine], so you know that this is the heart of your game logic. For reversi, it's very simple. Here's a diagram of our game state machine:<br />
<br />
[[File:reversi6.jpg]]<br />
<br />
And here's our "states.inc.php", according to this diagram:<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 10 )<br />
),<br />
<br />
10 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a disc'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a disc'),<br />
"type" => "activeplayer",<br />
"args" => "argPlayerTurn",<br />
"possibleactions" => array( 'playDisc' ),<br />
"transitions" => array( "playDisc" => 11, "zombiePass" => 11 )<br />
),<br />
<br />
11 => array(<br />
"name" => "nextPlayer",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "nextTurn" => 10, "cantPlay" => 11, "endGame" => 99 )<br />
),<br />
<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
Now, let's create in reversi.game.php the methods that are declared in this game states description file:<br />
* argPlayerTurn<br />
* stNextPlayer<br />
<br />
... and start a new Reversi game.<br />
<br />
As you can see on the screen capture below, the BGA framework makes the game jump to our first game state "playerTurn" right after the initial setup. That's why the status bar contains the description of playerTurn state ("XXXX must play a disc"):<br />
<br />
[[File:reversi7.jpg]]<br />
<br />
== The rules ==<br />
<br />
We will use the "getPossibleMoves" PHP method to:<br />
* Indicate to the current player where she is allowed to play by returning a list of coordinates<br />
* Check if the player has the right to play in the spot they choose<br />
<br />
<br />
This is pure PHP programming here, and there are no special things from the BGA framework that can be used. This is why we won't go into details here. The overall idea is:<br />
* Create a "getTurnedOverDiscs(x,y)" method that returns coordinates of discs that would be turned over if a token would be played at x,y.<br />
* Loop through all free squares of the board and call the "getTurnedOverDiscs" method on each of them. If at least 1 token is turned over, this is a valid move.<br />
<br />
IMPORTANT: Keep in mind that making a database query is slow, so please don't load the entire game board with a SQL query multiple times. In our implementation, we load the entire board once at the beginning of "getPossibleMoves", and then pass the board as an argument to all methods.<br />
<br />
If you want to look into details, please look at the "utility method" sections of reversi.game.php. If building the tutorial yourself, copy the functions under "Utility functions" comment from the Reversi tutorial.<br />
<br />
== Display allowed moves ==<br />
<br />
Now we want to highlight the squares where the player can place a disc.<br />
<br />
To do this, we add a "argPlayerTurn" method in reversi.game.php. This method is called on the server each time we enter into "playerTurn" game state, and its result is transferred automatically to the client-side:<br />
<br />
<pre><br />
function argPlayerTurn()<br />
{<br />
return array(<br />
'possibleMoves' => self::getPossibleMoves( self::getActivePlayerId() )<br />
);<br />
}<br />
</pre><br />
<br />
We use the "getPossibleMoves" method we just developed.<br />
<br />
Each time we enter into a new game state, we use the "onEnteringState" Javascript method (in the reversi.js file, under "Game & client states"). This lets us use the data returned by the method above on the client side.<br />
<br />
<pre><br />
onEnteringState: function( stateName, args )<br />
{<br />
console.log( 'Entering state: '+stateName );<br />
<br />
switch( stateName )<br />
{<br />
case 'playerTurn':<br />
this.updatePossibleMoves( args.args.possibleMoves );<br />
break;<br />
}<br />
},<br />
</pre><br />
<br />
So, when we enter into "playerTurn" game state, we call our "updatePossibleMoves" method (under the "Utility functions" section). This method looks like this:<br />
<br />
<pre><br />
updatePossibleMoves: function( possibleMoves )<br />
{<br />
// Remove current possible moves<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
<br />
for( var x in possibleMoves )<br />
{<br />
for( var y in possibleMoves[ x ] )<br />
{<br />
// x,y is a possible move<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
} <br />
}<br />
<br />
this.addTooltipToClass( 'possibleMove', '', _('Place a disc here') );<br />
},<br />
</pre><br />
Here's what this does. At first, it removes all "possibleMove" classes currently applied with the very useful combination of "dojo.query" and "removeClass" method.<br />
<br />
Then it loops through all possible moves our PHP "updatePossibleMoves" function created for us, and adds the "possibleMove" class to each corresponding square.<br />
<br />
Finally, it uses the BGA framework "addTooltipToClass" method to associate a tooltip to all those highlighted squares so that players can understand their meaning.<br />
<br />
To see the possible moves we need to create a CSS class ("possibleMove") that can be applied to a "square" element to highlight it:<br />
<pre><br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
</pre><br />
<br />
<br />
<br />
And here we are:<br />
<br />
[[File:reversi8.jpg.jpg]]<br />
<br />
== Let's play ==<br />
<br />
From now, it's better to restart a game with 2 players, because we are going to implement a complete Reversi turn. The summary of what we are going to do is:<br />
* When we click on a "possibleMove" square, send the move to the server.<br />
* Server side, check the move is correct, apply Reversi rules and jump to next player.<br />
* Client side, change the disc position to reflect the move.<br />
<br />
First we associate each click on a square to one of our methods using our Javascript "setup" method:<br />
<br />
<pre><br />
dojo.query( '.square' ).connect( 'onclick', this, 'onPlayDisc' );<br />
</pre><br />
<br />
Note the use of the "dojo.query" method to get all HTML elements with "square" class in just one function call. Now, our "onPlayDisc" method is called each time someone clicks on a square.<br />
<br />
Here's our "onPlayDisc" method below:<br />
<br />
<pre><br />
onPlayDisc: function( evt )<br />
{<br />
// Stop this event propagation<br />
dojo.stopEvent( evt );<br />
<br />
// Get the cliqued square x and y<br />
// Note: square id format is "square_X_Y"<br />
var coords = evt.currentTarget.id.split('_');<br />
var x = coords[1];<br />
var y = coords[2];<br />
<br />
if( ! dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{<br />
// This is not a possible move => the click does nothing<br />
return ;<br />
}<br />
<br />
if( this.checkAction( 'playDisc' ) ) // Check that this action is possible at this moment<br />
{ <br />
this.ajaxcall( "/reversi/reversi/playDisc.html", {<br />
x:x,<br />
y:y<br />
}, this, function( result ) {} );<br />
} <br />
},<br />
</pre><br />
<br />
What we do here is:<br />
* We stop the propagation of the Javascript "onclick" event. Otherwise, it can lead to random behavior so it's always a good idea.<br />
* We get the x/y coordinates of the square by using "evt.currentTarget.id".<br />
* We check that clicked square has the "possibleMove" class, otherwise we know for sure that we can't play there.<br />
* We check that "playDisc" action is possible, according to current game state (see "possibleactions" entry in our "playerTurn" game state defined above). This check is important to avoid issues if a player double clicks on a square.<br />
* Finally, we make a call to the server using BGA "ajaxcall" method with argument x and y. Be sure to update the first parameter to match your game if building the tutorial yourself. E.g. "/yourgamename/yourgamename/playDisc.html"<br />
<br />
Now, we have to manage this "playDisc" action on the server side. At first, we introduce a "playDisc" entry point in our "reversi.action.php":<br />
<br />
<pre><br />
public function playDisc()<br />
{<br />
self::setAjaxMode(); <br />
$x = self::getArg( "x", AT_posint, true );<br />
$y = self::getArg( "y", AT_posint, true );<br />
$result = $this->game->playDisc( $x, $y );<br />
self::ajaxResponse( );<br />
}<br />
</pre><br />
<br />
As you can see, we get the 2 arguments x and y from the javascript call, and call a corresponding "playDisc" method in our game logic (reversi.game.php).<br />
<br />
Now, let's have a look of this playDisc method:<br />
<br />
<pre><br />
function playDisc( $x, $y )<br />
{<br />
// Check that this player is active and that this action is possible at this moment<br />
self::checkAction( 'playDisc' ); <br />
</pre><br />
<br />
... at first, we check that this action is possible according to current game state (see "possible action"). We already did it on client side, but it's important to do it on server side too (otherwise it would be possible to cheat).<br />
<br />
<pre><br />
// Now, check if this is a possible move<br />
$board = self::getBoard();<br />
$player_id = self::getActivePlayerId();<br />
$turnedOverDiscs = self::getTurnedOverDiscs( $x, $y, $player_id, $board );<br />
<br />
if( count( $turnedOverDiscs ) > 0 )<br />
{<br />
// This move is possible!<br />
</pre><br />
<br />
...now, we are using the "getTurnedOverDiscs" method again to check that this move is possible.<br />
<br />
<pre><br />
// Let's place a disc at x,y and return all "$returned" discs to the active player<br />
<br />
$sql = "UPDATE board SET board_player='$player_id'<br />
WHERE ( board_x, board_y) IN ( ";<br />
<br />
foreach( $turnedOverDiscs as $turnedOver )<br />
{<br />
$sql .= "('".$turnedOver['x']."','".$turnedOver['y']."'),";<br />
}<br />
$sql .= "('$x','$y') ) ";<br />
<br />
self::DbQuery( $sql );<br />
<br />
</pre><br />
<br />
... we update the database to change the color of all turned over disc + the disc we just placed.<br />
<br />
<pre><br />
// Update scores according to the number of disc on board<br />
$sql = "UPDATE player<br />
SET player_score = (<br />
SELECT COUNT( board_x ) FROM board WHERE board_player=player_id<br />
)";<br />
self::DbQuery( $sql );<br />
<br />
// Statistics<br />
self::incStat( count( $turnedOverDiscs ), "turnedOver", $player_id );<br />
if( ($x==1 && $y==1) || ($x==8 && $y==1) || ($x==1 && $y==8) || ($x==8 && $y==8) )<br />
self::incStat( 1, 'discPlayedOnCorner', $player_id );<br />
else if( $x==1 || $x==8 || $y==1 || $y==8 )<br />
self::incStat( 1, 'discPlayedOnBorder', $player_id );<br />
else if( $x>=3 && $x<=6 && $y>=3 && $y<=6 )<br />
self::incStat( 1, 'discPlayedOnCenter', $player_id );<br />
<br />
</pre><br />
<br />
... now, we update both player score by counting all disc, and we manage game statistics.<br />
<br />
<pre><br />
// Notify<br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ), array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
<br />
self::notifyAllPlayers( "turnOverDiscs", '', array(<br />
'player_id' => $player_id,<br />
'turnedOver' => $turnedOverDiscs<br />
) );<br />
<br />
$newScores = self::getCollectionFromDb( "SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", "", array(<br />
"scores" => $newScores<br />
) );<br />
</pre><br />
<br />
... then we notify about all these changes. We are using for that 3 notifications ('playDisc', 'turnOverDiscs' and 'newScores' that we are going to implement on client side later). Note that the description of the 'playDisc' notification will be logged in the game log.<br />
<br />
<pre><br />
// Then, go to the next state<br />
$this->gamestate->nextState( 'playDisc' );<br />
}<br />
else<br />
throw new feException( "Impossible move" );<br />
}<br />
</pre><br />
<br />
... finally, we jump to the next game state if everything goes fine ('playDisc' is the name of a transition in the 'playerTurn' game state description above which leads to state 11 which is 'nextPlayer').<br />
<br />
To make the statistics work, we have to initialize them in stats.inc.php:<br />
<br />
<pre><br />
// Statistics existing for each player<br />
"player" => array(<br />
<br />
"discPlayedOnCorner" => array( "id"=> 10,<br />
"name" => totranslate("Discs played on a corner"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnBorder" => array( "id"=> 11,<br />
"name" => totranslate("Discs played on a border"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnCenter" => array( "id"=> 12,<br />
"name" => totranslate("Discs played on board center part"), <br />
"type" => "int" ),<br />
<br />
"turnedOver" => array( "id"=> 13,<br />
"name" => totranslate("Number of discs turned over"), <br />
"type" => "int" ) <br />
)<br />
</pre><br />
<br />
A last thing to do on the server side is to activate the next player when we enter the "nextPlayer" game state (in the "reversi.game.php" file, under "Game state reactions"):<br />
<br />
<pre><br />
function stNextPlayer()<br />
{<br />
// Active next player<br />
$player_id = self::activeNextPlayer();<br />
<br />
// Check if both player has at least 1 discs, and if there are free squares to play<br />
$player_to_discs = self::getCollectionFromDb( "SELECT board_player, COUNT( board_x )<br />
FROM board<br />
GROUP BY board_player", true );<br />
<br />
if( ! isset( $player_to_discs[ null ] ) )<br />
{<br />
// Index 0 has not been set => there's no more free place on the board !<br />
// => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
else if( ! isset( $player_to_discs[ $player_id ] ) )<br />
{<br />
// Active player has no more disc on the board => he looses immediately<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
<br />
// Can this player play?<br />
<br />
$possibleMoves = self::getPossibleMoves( $player_id );<br />
if( count( $possibleMoves ) == 0 )<br />
{<br />
<br />
// This player can't play<br />
// Can his opponent play ?<br />
$opponent_id = self::getUniqueValueFromDb( "SELECT player_id FROM player WHERE player_id!='$player_id' " );<br />
if( count( self::getPossibleMoves( $opponent_id ) ) == 0 )<br />
{<br />
// Nobody can move => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
}<br />
else<br />
{ <br />
// => pass his turn<br />
$this->gamestate->nextState( 'cantPlay' );<br />
}<br />
}<br />
else<br />
{<br />
// This player can play. Give him some extra time<br />
self::giveExtraTime( $player_id );<br />
$this->gamestate->nextState( 'nextTurn' );<br />
}<br />
<br />
}<br />
</pre><br />
<br />
Now, when we play a disc, the rules are checked and the disc appears in the database.<br />
<br />
[[File:reversi9.jpg]]<br />
<br />
Of course, as we don't manage notifications on client side, we need to press F5 after each move to see the changes on the board.<br />
<br />
== Make the move appear automatically ==<br />
<br />
Now, what we have to do is process the notifications sent by the server and make the move appear on the interface.<br />
<br />
In our "setupNotifications" method, we register 2 methods for the 2 notifications we created at the previous step ('playDisc' and 'turnOverDiscs'):<br />
<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 );<br />
dojo.subscribe( 'turnOverDiscs', this, "notif_turnOverDiscs" );<br />
this.notifqueue.setSynchronous( 'turnOverDiscs', 1500 );<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
this.notifqueue.setSynchronous( 'newScores', 500 );<br />
</pre><br />
<br />
As you can see, we associate each of our 3 notifications with a method prefixed with "notif_". We also define these notifications as "synchronous", with a duration in millisecond. It tells the user interface to wait some time after executing the notification, to let the animation end before starting the next notification. In our specific case, the animation will be the following:<br />
* Make a disc slide from the player panel to its place on the board<br />
* (wait 500ms)<br />
* Make all turned over discs blink (and of course turned them over)<br />
* (wait 1500ms)<br />
* Update the player scores<br />
* (wait 500ms)<br />
<br />
The 2nd parameter in dodo.subscribe call (this) is the 'context', and will be passed in as a parameter to the specified method.<br />
<br />
Let's have a look now on the "playDisc" notification handler method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addTokenOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
No surprise here, we re-used some existing stuff to:<br />
* Remove the highlighted squares.<br />
* Add a new disc on board, coming from player panel.<br />
<br />
Now, here's the method that handles the turnOverDiscs notification:<br />
<br />
<pre><br />
notif_turnOverDiscs: function( notif )<br />
{<br />
// Get the color of the player who is returning the discs<br />
var targetColor = this.gamedatas.players[ notif.args.player_id ].color;<br />
<br />
// Make these discs blink and set them to the specified color<br />
for( var i in notif.args.turnedOver )<br />
{<br />
var token = notif.args.turnedOver[ i ];<br />
<br />
// Make the token blink 2 times<br />
var anim = dojo.fx.chain( [<br />
dojo.fadeOut( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeOut( { <br />
node: 'token_'+token.x+'_'+token.y,<br />
onEnd: function( node ) {<br />
<br />
// Remove any color class<br />
dojo.removeClass( node, [ 'tokencolor_000000', 'tokencolor_ffffff' ] );<br />
// ... and add the good one<br />
dojo.addClass( node, 'tokencolor_'+targetColor );<br />
<br />
} <br />
} ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } )<br />
<br />
] ); // end of dojo.fx.chain<br />
<br />
// ... and launch the animation<br />
anim.play(); <br />
}<br />
},<br />
</pre><br />
<br />
The list of the discs to be turned over has been made available by our server side code in "notif.args.turnedOver" (see previous paragraph). We loop through all these discs, and create a complex animation using dojo.Animation for each of them. The complete documentation on dojo animations [http://dojotoolkit.org/documentation/tutorials/1.6/animation/ can be found here].<br />
<br />
In few words: we create a chain of 4 animations to make the disc fade out, fade in, fade out again, and fade in again. At the end of the second fade out, we change the color of the disc. Finally, we launch the animation with "play()".<br />
<br />
And Also the notification to update the scores:<br />
<br />
<pre><br />
notif_newScores: function( notif )<br />
{<br />
for( var player_id in notif.args.scores )<br />
{<br />
var newScore = notif.args.scores[ player_id ];<br />
this.scoreCtrl[ player_id ].toValue( newScore );<br />
}<br />
},<br />
</pre></div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Tutorial_reversi&diff=8350
Tutorial reversi
2021-05-26T02:01:01Z
<p>JonChambers: /* The discs */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Reversi.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the [http://en.wikipedia.org/wiki/Reversi#Rules rules of Reversi].<br />
* Know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
<br />
== Create your first game ==<br />
<br />
With the initial skeleton of code provided initially, you can already start a game from the BGA Studio. For now, we are going to work with one player only. Most of the time it is simpler to proceed with only one player during the early phase of development of your game, as it's easy and fast to start/stop games.<br />
<br />
(If you choose to start with 2 players, you should see two names on the right: testdude0 and testdude1. To switch between them, press the red arrow button near their names; it will open another tab. This way you don't need to login and logout from multiple accounts.) <br />
<br />
Reminder: Always use the "Express Start" button to start the game. <br />
<br />
Thus, you can start a "Reversi" game, and arrive on a void, empty game. Yeah.<br />
<br />
== Let it look like Reversi ==<br />
<br />
It's always a good idea to start with a little bit of graphic work. Why? Because this helps to figure out how the final game will be, and issues that may appear later.<br />
<br />
Be careful designing the layout of your game: you must always keep in mind that players with a 1024px screen width must be able to play. Usually, it means that the width of the play area can be 750px (in the worst case).<br />
<br />
For Reversi, it's useless to have a 750x750px board - much too big, so we choose this one which fit perfectly (536x528):<br />
<br />
[[File:Board.jpg]]<br />
<br />
Note that we are using a jpg file. Jpg are lighter than png, so faster to load. Later we are going to use PNGs for discs for transparency purpose.<br />
<br />
Now, let's make it appear in our game:<br />
* upload board.jpg in your "img/" directory.<br />
* edit "reversi_reversi.tpl" to add a 'div' for your board.<br />
<br />
Note: If you are building this game by following the tutorial, you will have a different project name than 'reversi'. The file names in your project will be different than shown in this tutorial, replacing 'reversi' with your project name. It should be trivial to find the right file in your project, but be sure that any code (other than comments) that references 'reversi' is changed to your actual project name.<br />
<br />
<pre><br />
<div id="board"><br />
</div><br />
</pre><br />
<br />
* edit your reversi.css file to transform it into a visible board:<br />
<br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
}<br />
<br />
Refresh your page. Here's your board:<br />
<br />
[[File:reversi1.jpg]]<br />
<br />
== Make the squares appear ==<br />
<br />
Now, we need to create some invisible HTML elements where squares are. These elements will be used as position references for white and black discs. <br />
We need 64 squares. To avoid writing 64 'div' elements on our template, we are going to use the "block" feature.<br />
<br />
Let's modify our template like this:<br />
<br />
<pre><br />
<div id="board"><br />
<!-- BEGIN square --><br />
<div id="square_{X}_{Y}" class="square" style="left: {LEFT}px; top: {TOP}px;"></div><br />
<!-- END square --><br />
</div><br />
</pre><br />
<br />
As you can see, we created a "square" block, with 4 variable elements: X, Y, LEFT and TOP. We are going to use this block 64 times during page load.<br />
<br />
Let's do it in our "reversi.view.php" file:<br />
<br />
<pre><br />
$this->page->begin_block( "reversi_reversi", "square" );<br />
<br />
$hor_scale = 64.8;<br />
$ver_scale = 64.4;<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$this->page->insert_block( "square", array(<br />
'X' => $x,<br />
'Y' => $y,<br />
'LEFT' => round( ($x-1)*$hor_scale+10 ),<br />
'TOP' => round( ($y-1)*$ver_scale+7 )<br />
) );<br />
} <br />
}<br />
</pre><br />
<br />
Note: as you can see, squares in our "board.jpg" files do not have an exact width/height in pixels, and that's the reason we are using floating point numbers here.<br />
<br />
Now, to finish our work and check if everything works fine, we are going to style our square a little bit in our CSS stylesheet:<br />
<br />
<pre><br />
#board {<br />
width: 536px;<br />
height: 528px;<br />
background-image: url('img/board.jpg');<br />
position: relative;<br />
}<br />
<br />
.square {<br />
width: 62px;<br />
height: 62px;<br />
position: absolute;<br />
background-color: red;<br />
}<br />
</pre><br />
<br />
Explanations:<br />
* With "position: relative" on board, we ensure square elements are positioned relatively to board.<br />
* For the test, we use a red background color for the square. This is a useful tip to figure out if everything is fine with invisible elements.<br />
<br />
Let's refresh and check our our (beautiful) squares:<br />
<br />
[[File:reversi2.jpg]]<br />
<br />
Hint: Now that we know our squares are set up correctly, we can hide the red background. You can remove the "background-color: red;" line from your .square class in the CSS stylesheet.<br />
<br />
== The discs ==<br />
<br />
Now, our board is ready to receive some disc tokens!<br />
<br />
[Note: Throughout this tutorial, sometimes "tokens" is used, and sometimes "discs" is used. They are often swapped if you're looking at code in the reversi example project.]<br />
<br />
At first, we introduce a new 'div' element as a child of "board" to host all these tokens (in our template):<br />
<br />
<pre><br />
<!-- END square --><br />
<br />
<div id="tokens"><br />
</div><br />
</div><br />
</pre><br />
<br />
Then, let's introduce a new piece of art with the discs. We need some transparency here so we are using a png file:<br />
<br />
[[File:tokens.png]]<br />
<br />
Upload this image file "tokens.png" in your "img/" directory.<br />
<br />
Important: we are using ONE file for both discs. It's really important that you use a minimum number of graphic files for your game with this "CSS sprite" technique, because it makes the game loading faster and more reliable. [http://www.w3schools.com/css/css_image_sprites.asp Read more about CSS sprites].<br />
<br />
Now, let's separate the disc with some CSS stuff:<br />
<br />
<pre><br />
.token {<br />
width: 56px;<br />
height: 56px;<br />
position: absolute;<br />
background-image: url('img/tokens.png');<br />
}<br />
.tokencolor_ffffff { background-position: 0px 0px; }<br />
.tokencolor_000000 { background-position: -56px 0px; }<br />
</pre><br />
<br />
With this CSS code, we apply the classes "token" and "tokencolor_ffffff" to a div element and we've got a white token. Yeah.<br />
<br />
Note the "position: absolute" which allows us to position tokens on the board and make them "slide" to their positions.<br />
<br />
Now, let's make a first token appear on our board. Disc tokens are not visible at the beginning of the game: they appear dynamically during the game. For this reason, we are going to make them appear from our Javascript code, with a BGA Framework technique called "JS template".<br />
<br />
In our template file (reversi_reversi.tpl), let's create the piece of HTML needed to display our token:<br />
<br />
<pre><br />
<script type="text/javascript"><br />
<br />
// Templates<br />
<br />
var jstpl_token='<div class="token tokencolor_${color}" id="token_${x_y}"></div>';<br />
<br />
</script><br />
</pre><br />
<br />
Note: we already created the "templates" section for you in the game skeleton.<br />
<br />
As you can see, we defined a JS template named "jstpl_token" with a piece of HTML and two variables: the color of the token and its x/y coordinates. Note that the syntax of the argument is different for template block variables (braces <code>{}</code>) and JS template variables (dollar and braces <code>${}</code>).<br />
<br />
Now, let's create a method in our Javascript code (in the "reversi.js" file) that will make a token appear on the board, using this template. Add under the section //// Utility methods:<br />
<br />
<pre><br />
addTokenOnBoard: function( x, y, player )<br />
{<br />
dojo.place( this.format_block( 'jstpl_token', {<br />
x_y: x+'_'+y,<br />
color: this.gamedatas.players[ player ].color<br />
} ) , 'tokens' );<br />
<br />
this.placeOnObject( 'token_'+x+'_'+y, 'overall_player_board_'+player );<br />
this.slideToObject( 'token_'+x+'_'+y, 'square_'+x+'_'+y ).play();<br />
},<br />
</pre><br />
<br />
At first, with "dojo.place" and "this.format_block" methods, we create a HTML piece of code and insert it as a new child of "tokens" div element.<br />
<br />
"'overall_player_board_'+player" refers to the div element that contains each player's information and avatar. By initially placing the token here, it gives the effect that the player's avatar is throwing the token onto the board.<br />
<br />
Then, with BGA "this.placeOnObject" method, we place this element over the panel of some player. Immediately after, using BGA "this.slideToObject" method, we make the disc slide to the "square" element, its final destination.<br />
<br />
Note: don't forget to call the "play()", otherwise the token remains at its original location.<br />
<br />
Note: note that during all the process, the parent of the new disc HTML element will remain "tokens". placeOnObject and slideToObject methods are only moving the position of elements on screen, and they are not modifying the HTML tree.<br />
<br />
Before we can show a token, we need to set the player colors in the setupNewGame function in reversi.game.php:<br />
<pre><br />
$default_colors = array( "ffffff", "000000" );<br />
</pre><br />
<br />
Note: Probably you'll have to remove the line "self::reattributeColorsBasedOnPreferences( $players, $gameinfos['player_colors'] );".<br />
<br />
<br />
Now, to test if everything works fine, just add "this.addTokenOnBoard( 2, 2, [player_id] )" in the "setup" Javascript method in reversi.js, and reload the page.<br />
<br />
<br />
A token should appear and slide immediately to its position, like this:<br />
<br />
[[File:reversi3.jpg]]<br />
<br />
== The database ==<br />
<br />
We did most of the client-side programming, so let's have a look on the other side now.<br />
<br />
To design the database model of our game, the best thing to do is to follow the "Go to game database" link at the bottom of our game, to access the database directly with a [http://www.phpmyadmin.net/ PhpMyAdmin] instance. Your PhpMyAdmin username/password is in your welcome email (and currently the same as the SFTP username/password).<br />
<br />
Then, you can create the tables you need for your table (do not remove existing tables!), and report every SQL command used in your "dbmodel.sql" file. How do you generate SQL to create table after creating the table in the UI? See [https://www.itsupportguides.com/knowledge-base/tech-tips-tricks/how-to-generate-sql-create-table-script-using-phpmyadmin/ here]. Add 'IF NOT EXISTS' to the CREATE TABLE sql (see example below).<br />
<br />
[[File:reversi4.jpg]]<br />
<br />
The database model of Reversi is very simple: just one table with the squares of the board. <br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `board` (<br />
`board_x` smallint(5) unsigned NOT NULL,<br />
`board_y` smallint(5) unsigned NOT NULL,<br />
`board_player` int(10) unsigned DEFAULT NULL,<br />
PRIMARY KEY (`board_x`,`board_y`)<br />
) ENGINE=InnoDB;<br />
</pre><br />
<br />
Add the above SQL to dbmodel.sql. Now, a new database with a "board" table will be created each time we start a Reversi game. This is why after modifying our dbmodel.sql it's a good time to stop & start again our game.<br />
<br />
== Setup the initial game position ==<br />
<br />
The "setupNewGame" method of our reversi.game.php is called during initial setup: this is the place to initialize our data and to place the initial tokens on the board (initially, there are 4 tokens on the board).<br />
<br />
Let's do this:<br />
<br />
<pre><br />
// Init the board<br />
$sql = "INSERT INTO board (board_x,board_y,board_player) VALUES ";<br />
$sql_values = array();<br />
list( $blackplayer_id, $whiteplayer_id ) = array_keys( $players );<br />
for( $x=1; $x<=8; $x++ )<br />
{<br />
for( $y=1; $y<=8; $y++ )<br />
{<br />
$token_value = "NULL";<br />
if( ($x==4 && $y==4) || ($x==5 && $y==5) ) // Initial positions of white player<br />
$token_value = "'$whiteplayer_id'";<br />
else if( ($x==4 && $y==5) || ($x==5 && $y==4) ) // Initial positions of black player<br />
$token_value = "'$blackplayer_id'";<br />
<br />
$sql_values[] = "('$x','$y',$token_value)";<br />
}<br />
}<br />
$sql .= implode( ',', $sql_values );<br />
self::DbQuery( $sql );<br />
<br />
<br />
// Active first player<br />
self::activeNextPlayer(); <br />
</pre><br />
<br />
We create one table entry for each square, with a "NULL" value which means "empty square". For 4 specific squares, we place an initial token.<br />
<br />
At the end, we call activeNextPlayer to make the first player active at the beginning of the game.<br />
<br />
You may need to remove the call to self::reattributeColorsBasedOnPreferences() in SetupNewGame() so that any player color preferences do not override the two colors supported here.<br />
<br />
Now, we need to make these tokens appear on the client side. The first step is to return the token positions with our "getAllDatas" PHP method (called during each page reload):<br />
<br />
<pre><br />
// Get reversi board token<br />
$result['board'] = self::getObjectListFromDB( "SELECT board_x x, board_y y, board_player player<br />
FROM board<br />
WHERE board_player IS NOT NULL" );<br />
</pre><br />
<br />
We are using the BGA framework "getObjectListFromDB" method that formats the result of this SQL query in a PHP array with x, y and player attributes.<br />
<br />
Last, we process this array client side, and place a disc token on the board for each array item. We do this using our Javascript "setup" method:<br />
<br />
<pre><br />
for( var i in gamedatas.board )<br />
{<br />
var square = gamedatas.board[i];<br />
<br />
if( square.player !== null )<br />
{<br />
this.addTokenOnBoard( square.x, square.y, square.player );<br />
}<br />
}<br />
</pre><br />
<br />
Our "board" entry created in "getAllDatas" can be used here as "gamedatas.board" in our Javascript. We are using our previously developed "addTokenOnBoard" method.<br />
<br />
Reload... and here we are:<br />
<br />
[[File:reversi5.jpg]]<br />
<br />
It starts to smell Reversi here...<br />
<br />
== The game state machine ==<br />
<br />
Now, let's stop our game again, because we are going to start the core game logic.<br />
<br />
You already read [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine], so you know that this is the heart of your game logic. For reversi, it's very simple. Here's a diagram of our game state machine:<br />
<br />
[[File:reversi6.jpg]]<br />
<br />
And here's our "states.inc.php", according to this diagram:<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 10 )<br />
),<br />
<br />
10 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a disc'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a disc'),<br />
"type" => "activeplayer",<br />
"args" => "argPlayerTurn",<br />
"possibleactions" => array( 'playDisc' ),<br />
"transitions" => array( "playDisc" => 11, "zombiePass" => 11 )<br />
),<br />
<br />
11 => array(<br />
"name" => "nextPlayer",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "nextTurn" => 10, "cantPlay" => 11, "endGame" => 99 )<br />
),<br />
<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
Now, let's create in reversi.game.php the methods that are declared in this game states description file:<br />
* argPlayerTurn<br />
* stNextPlayer<br />
<br />
... and start a new Reversi game.<br />
<br />
As you can see on the screen capture below, the BGA framework makes the game jump to our first game state "playerTurn" right after the initial setup. That's why the status bar contains the description of playerTurn state ("XXXX must play a disc"):<br />
<br />
[[File:reversi7.jpg]]<br />
<br />
== The rules ==<br />
<br />
We will use the "getPossibleMoves" PHP method to:<br />
* Indicate to the current player where she is allowed to play by returning a list of coordinates<br />
* Check if the player has the right to play in the spot they choose<br />
<br />
<br />
This is pure PHP programming here, and there are no special things from the BGA framework that can be used. This is why we won't go into details here. The overall idea is:<br />
* Create a "getTurnedOverDiscs(x,y)" method that returns coordinates of discs that would be turned over if a token would be played at x,y.<br />
* Loop through all free squares of the board and call the "getTurnedOverDiscs" method on each of them. If at least 1 token is turned over, this is a valid move.<br />
<br />
IMPORTANT: Keep in mind that making a database query is slow, so please don't load the entire game board with a SQL query multiple times. In our implementation, we load the entire board once at the beginning of "getPossibleMoves", and then pass the board as an argument to all methods.<br />
<br />
If you want to look into details, please look at the "utility method" sections of reversi.game.php. If building the tutorial yourself, copy the functions under "Utility functions" comment from the Reversi tutorial.<br />
<br />
== Display allowed moves ==<br />
<br />
Now we want to highlight the squares where the player can place a disc.<br />
<br />
To do this, we add a "argPlayerTurn" method in reversi.game.php. This method is called on the server each time we enter into "playerTurn" game state, and its result is transferred automatically to the client-side:<br />
<br />
<pre><br />
function argPlayerTurn()<br />
{<br />
return array(<br />
'possibleMoves' => self::getPossibleMoves( self::getActivePlayerId() )<br />
);<br />
}<br />
</pre><br />
<br />
We use the "getPossibleMoves" method we just developed.<br />
<br />
Each time we enter into a new game state, we use the "onEnteringState" Javascript method (in the reversi.js file, under "Game & client states"). This lets us use the data returned by the method above on the client side.<br />
<br />
<pre><br />
onEnteringState: function( stateName, args )<br />
{<br />
console.log( 'Entering state: '+stateName );<br />
<br />
switch( stateName )<br />
{<br />
case 'playerTurn':<br />
this.updatePossibleMoves( args.args.possibleMoves );<br />
break;<br />
}<br />
},<br />
</pre><br />
<br />
So, when we enter into "playerTurn" game state, we call our "updatePossibleMoves" method (under the "Utility functions" section). This method looks like this:<br />
<br />
<pre><br />
updatePossibleMoves: function( possibleMoves )<br />
{<br />
// Remove current possible moves<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
<br />
for( var x in possibleMoves )<br />
{<br />
for( var y in possibleMoves[ x ] )<br />
{<br />
// x,y is a possible move<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
} <br />
}<br />
<br />
this.addTooltipToClass( 'possibleMove', '', _('Place a disc here') );<br />
},<br />
</pre><br />
Here's what this does. At first, it removes all "possibleMove" classes currently applied with the very useful combination of "dojo.query" and "removeClass" method.<br />
<br />
Then it loops through all possible moves our PHP "updatePossibleMoves" function created for us, and adds the "possibleMove" class to each corresponding square.<br />
<br />
Finally, it uses the BGA framework "addTooltipToClass" method to associate a tooltip to all those highlighted squares so that players can understand their meaning.<br />
<br />
To see the possible moves we need to create a CSS class ("possibleMove") that can be applied to a "square" element to highlight it:<br />
<pre><br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
</pre><br />
<br />
<br />
<br />
And here we are:<br />
<br />
[[File:reversi8.jpg.jpg]]<br />
<br />
== Let's play ==<br />
<br />
From now, it's better to restart a game with 2 players, because we are going to implement a complete Reversi turn. The summary of what we are going to do is:<br />
* When we click on a "possibleMove" square, send the move to the server.<br />
* Server side, check the move is correct, apply Reversi rules and jump to next player.<br />
* Client side, change the disc position to reflect the move.<br />
<br />
First we associate each click on a square to one of our methods using our Javascript "setup" method:<br />
<br />
<pre><br />
dojo.query( '.square' ).connect( 'onclick', this, 'onPlayDisc' );<br />
</pre><br />
<br />
Note the use of the "dojo.query" method to get all HTML elements with "square" class in just one function call. Now, our "onPlayDisc" method is called each time someone clicks on a square.<br />
<br />
Here's our "onPlayDisc" method below:<br />
<br />
<pre><br />
onPlayDisc: function( evt )<br />
{<br />
// Stop this event propagation<br />
dojo.stopEvent( evt );<br />
<br />
// Get the cliqued square x and y<br />
// Note: square id format is "square_X_Y"<br />
var coords = evt.currentTarget.id.split('_');<br />
var x = coords[1];<br />
var y = coords[2];<br />
<br />
if( ! dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{<br />
// This is not a possible move => the click does nothing<br />
return ;<br />
}<br />
<br />
if( this.checkAction( 'playDisc' ) ) // Check that this action is possible at this moment<br />
{ <br />
this.ajaxcall( "/reversi/reversi/playDisc.html", {<br />
x:x,<br />
y:y<br />
}, this, function( result ) {} );<br />
} <br />
},<br />
</pre><br />
<br />
What we do here is:<br />
* We stop the propagation of the Javascript "onclick" event. Otherwise, it can lead to random behavior so it's always a good idea.<br />
* We get the x/y coordinates of the square by using "evt.currentTarget.id".<br />
* We check that clicked square has the "possibleMove" class, otherwise we know for sure that we can't play there.<br />
* We check that "playDisc" action is possible, according to current game state (see "possibleactions" entry in our "playerTurn" game state defined above). This check is important to avoid issues if a player double clicks on a square.<br />
* Finally, we make a call to the server using BGA "ajaxcall" method with argument x and y. Be sure to update the first parameter to match your game if building the tutorial yourself. E.g. "/yourgamename/yourgamename/playDisc.html"<br />
<br />
Now, we have to manage this "playDisc" action on the server side. At first, we introduce a "playDisc" entry point in our "reversi.action.php":<br />
<br />
<pre><br />
public function playDisc()<br />
{<br />
self::setAjaxMode(); <br />
$x = self::getArg( "x", AT_posint, true );<br />
$y = self::getArg( "y", AT_posint, true );<br />
$result = $this->game->playDisc( $x, $y );<br />
self::ajaxResponse( );<br />
}<br />
</pre><br />
<br />
As you can see, we get the 2 arguments x and y from the javascript call, and call a corresponding "playDisc" method in our game logic (reversi.game.php).<br />
<br />
Now, let's have a look of this playDisc method:<br />
<br />
<pre><br />
function playDisc( $x, $y )<br />
{<br />
// Check that this player is active and that this action is possible at this moment<br />
self::checkAction( 'playDisc' ); <br />
</pre><br />
<br />
... at first, we check that this action is possible according to current game state (see "possible action"). We already did it on client side, but it's important to do it on server side too (otherwise it would be possible to cheat).<br />
<br />
<pre><br />
// Now, check if this is a possible move<br />
$board = self::getBoard();<br />
$player_id = self::getActivePlayerId();<br />
$turnedOverDiscs = self::getTurnedOverDiscs( $x, $y, $player_id, $board );<br />
<br />
if( count( $turnedOverDiscs ) > 0 )<br />
{<br />
// This move is possible!<br />
</pre><br />
<br />
...now, we are using the "getTurnedOverDiscs" method again to check that this move is possible.<br />
<br />
<pre><br />
// Let's place a disc at x,y and return all "$returned" discs to the active player<br />
<br />
$sql = "UPDATE board SET board_player='$player_id'<br />
WHERE ( board_x, board_y) IN ( ";<br />
<br />
foreach( $turnedOverDiscs as $turnedOver )<br />
{<br />
$sql .= "('".$turnedOver['x']."','".$turnedOver['y']."'),";<br />
}<br />
$sql .= "('$x','$y') ) ";<br />
<br />
self::DbQuery( $sql );<br />
<br />
</pre><br />
<br />
... we update the database to change the color of all turned over disc + the disc we just placed.<br />
<br />
<pre><br />
// Update scores according to the number of disc on board<br />
$sql = "UPDATE player<br />
SET player_score = (<br />
SELECT COUNT( board_x ) FROM board WHERE board_player=player_id<br />
)";<br />
self::DbQuery( $sql );<br />
<br />
// Statistics<br />
self::incStat( count( $turnedOverDiscs ), "turnedOver", $player_id );<br />
if( ($x==1 && $y==1) || ($x==8 && $y==1) || ($x==1 && $y==8) || ($x==8 && $y==8) )<br />
self::incStat( 1, 'discPlayedOnCorner', $player_id );<br />
else if( $x==1 || $x==8 || $y==1 || $y==8 )<br />
self::incStat( 1, 'discPlayedOnBorder', $player_id );<br />
else if( $x>=3 && $x<=6 && $y>=3 && $y<=6 )<br />
self::incStat( 1, 'discPlayedOnCenter', $player_id );<br />
<br />
</pre><br />
<br />
... now, we update both player score by counting all disc, and we manage game statistics.<br />
<br />
<pre><br />
// Notify<br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ), array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
<br />
self::notifyAllPlayers( "turnOverDiscs", '', array(<br />
'player_id' => $player_id,<br />
'turnedOver' => $turnedOverDiscs<br />
) );<br />
<br />
$newScores = self::getCollectionFromDb( "SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", "", array(<br />
"scores" => $newScores<br />
) );<br />
</pre><br />
<br />
... then we notify about all these changes. We are using for that 3 notifications ('playDisc', 'turnOverDiscs' and 'newScores' that we are going to implement on client side later). Note that the description of the 'playDisc' notification will be logged in the game log.<br />
<br />
<pre><br />
// Then, go to the next state<br />
$this->gamestate->nextState( 'playDisc' );<br />
}<br />
else<br />
throw new feException( "Impossible move" );<br />
}<br />
</pre><br />
<br />
... finally, we jump to the next game state if everything goes fine ('playDisc' is the name of a transition in the 'playerTurn' game state description above which leads to state 11 which is 'nextPlayer').<br />
<br />
To make the statistics work, we have to initialize them in stats.inc.php:<br />
<br />
<pre><br />
// Statistics existing for each player<br />
"player" => array(<br />
<br />
"discPlayedOnCorner" => array( "id"=> 10,<br />
"name" => totranslate("Discs played on a corner"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnBorder" => array( "id"=> 11,<br />
"name" => totranslate("Discs played on a border"), <br />
"type" => "int" ),<br />
<br />
"discPlayedOnCenter" => array( "id"=> 12,<br />
"name" => totranslate("Discs played on board center part"), <br />
"type" => "int" ),<br />
<br />
"turnedOver" => array( "id"=> 13,<br />
"name" => totranslate("Number of discs turned over"), <br />
"type" => "int" ) <br />
)<br />
</pre><br />
<br />
A last thing to do on the server side is to activate the next player when we enter the "nextPlayer" game state (in the "reversi.game.php" file, under "Game state reactions"):<br />
<br />
<pre><br />
function stNextPlayer()<br />
{<br />
// Active next player<br />
$player_id = self::activeNextPlayer();<br />
<br />
// Check if both player has at least 1 discs, and if there are free squares to play<br />
$player_to_discs = self::getCollectionFromDb( "SELECT board_player, COUNT( board_x )<br />
FROM board<br />
GROUP BY board_player", true );<br />
<br />
if( ! isset( $player_to_discs[ null ] ) )<br />
{<br />
// Index 0 has not been set => there's no more free place on the board !<br />
// => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
else if( ! isset( $player_to_discs[ $player_id ] ) )<br />
{<br />
// Active player has no more disc on the board => he looses immediately<br />
$this->gamestate->nextState( 'endGame' );<br />
return ;<br />
}<br />
<br />
// Can this player play?<br />
<br />
$possibleMoves = self::getPossibleMoves( $player_id );<br />
if( count( $possibleMoves ) == 0 )<br />
{<br />
<br />
// This player can't play<br />
// Can his opponent play ?<br />
$opponent_id = self::getUniqueValueFromDb( "SELECT player_id FROM player WHERE player_id!='$player_id' " );<br />
if( count( self::getPossibleMoves( $opponent_id ) ) == 0 )<br />
{<br />
// Nobody can move => end of the game<br />
$this->gamestate->nextState( 'endGame' );<br />
}<br />
else<br />
{ <br />
// => pass his turn<br />
$this->gamestate->nextState( 'cantPlay' );<br />
}<br />
}<br />
else<br />
{<br />
// This player can play. Give him some extra time<br />
self::giveExtraTime( $player_id );<br />
$this->gamestate->nextState( 'nextTurn' );<br />
}<br />
<br />
}<br />
</pre><br />
<br />
Now, when we play a disc, the rules are checked and the disc appears in the database.<br />
<br />
[[File:reversi9.jpg]]<br />
<br />
Of course, as we don't manage notifications on client side, we need to press F5 after each move to see the changes on the board.<br />
<br />
== Make the move appear automatically ==<br />
<br />
Now, what we have to do is process the notifications sent by the server and make the move appear on the interface.<br />
<br />
In our "setupNotifications" method, we register 2 methods for the 2 notifications we created at the previous step ('playDisc' and 'turnOverDiscs'):<br />
<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 );<br />
dojo.subscribe( 'turnOverDiscs', this, "notif_turnOverDiscs" );<br />
this.notifqueue.setSynchronous( 'turnOverDiscs', 1500 );<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
this.notifqueue.setSynchronous( 'newScores', 500 );<br />
</pre><br />
<br />
As you can see, we associate each of our 3 notifications with a method prefixed with "notif_". We also define these notifications as "synchronous", with a duration in millisecond. It tells the user interface to wait some time after executing the notification, to let the animation end before starting the next notification. In our specific case, the animation will be the following:<br />
* Make a disc slide from the player panel to its place on the board<br />
* (wait 500ms)<br />
* Make all turned over discs blink (and of course turned them over)<br />
* (wait 1500ms)<br />
* Update the player scores<br />
* (wait 500ms)<br />
<br />
The 2nd parameter in dodo.subscribe call (this) is the 'context', and will be passed in as a parameter to the specified method.<br />
<br />
Let's have a look now on the "playDisc" notification handler method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addTokenOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
No surprise here, we re-used some existing stuff to:<br />
* Remove the highlighted squares.<br />
* Add a new disc on board, coming from player panel.<br />
<br />
Now, here's the method that handles the turnOverDiscs notification:<br />
<br />
<pre><br />
notif_turnOverDiscs: function( notif )<br />
{<br />
// Get the color of the player who is returning the discs<br />
var targetColor = this.gamedatas.players[ notif.args.player_id ].color;<br />
<br />
// Make these discs blink and set them to the specified color<br />
for( var i in notif.args.turnedOver )<br />
{<br />
var token = notif.args.turnedOver[ i ];<br />
<br />
// Make the token blink 2 times<br />
var anim = dojo.fx.chain( [<br />
dojo.fadeOut( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } ),<br />
dojo.fadeOut( { <br />
node: 'token_'+token.x+'_'+token.y,<br />
onEnd: function( node ) {<br />
<br />
// Remove any color class<br />
dojo.removeClass( node, [ 'tokencolor_000000', 'tokencolor_ffffff' ] );<br />
// ... and add the good one<br />
dojo.addClass( node, 'tokencolor_'+targetColor );<br />
<br />
} <br />
} ),<br />
dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } )<br />
<br />
] ); // end of dojo.fx.chain<br />
<br />
// ... and launch the animation<br />
anim.play(); <br />
}<br />
},<br />
</pre><br />
<br />
The list of the discs to be turned over has been made available by our server side code in "notif.args.turnedOver" (see previous paragraph). We loop through all these discs, and create a complex animation using dojo.Animation for each of them. The complete documentation on dojo animations [http://dojotoolkit.org/documentation/tutorials/1.6/animation/ can be found here].<br />
<br />
In few words: we create a chain of 4 animations to make the disc fade out, fade in, fade out again, and fade in again. At the end of the second fade out, we change the color of the disc. Finally, we launch the animation with "play()".<br />
<br />
And Also the notification to update the scores:<br />
<br />
<pre><br />
notif_newScores: function( notif )<br />
{<br />
for( var player_id in notif.args.scores )<br />
{<br />
var newScore = notif.args.scores[ player_id ];<br />
this.scoreCtrl[ player_id ].toValue( newScore );<br />
}<br />
},<br />
</pre></div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Translations&diff=8295
Translations
2021-05-19T04:49:16Z
<p>JonChambers: /* On server side (PHP) */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
<br />
Using BGA Studio, the game you create is ready to be translated to each language by the BGA community. To make this possible, you only need to specify which string must be translated and how to combine them.<br />
<br />
== How translation works? ==<br />
<br />
When developing your game, all strings must be in English. Strings must be coherent with the English version of the game.<br />
<br />
Before the release of the game, BGA team will do the French translation of the game.<br />
<br />
After the release of the game, the BGA players community will translate the game in every language.<br />
<br />
== What should be translated? ==<br />
<br />
Every text that can be visible by the player when the game is running normally. This includes tooltips, texts on cards, error messages, ...<br />
<br />
This does NOT include error messages that are not supposed to happen (unexpected errors).<br />
<br />
== What rules should I follow for the original English strings? ==<br />
<br />
For a coherent and homogeneous interface, here are some rules about ending a sentence with a final period '.'<br />
<br />
* As a general rule:<br />
** If a sentence is displayed isolated in the interface => no final period<br />
** If a sentence is followed or could be followed by another sentence in the same interface space => final period.<br />
<br />
* In detail:<br />
** No final period:<br />
*** button labels<br />
*** section titles<br />
*** menu elements<br />
*** links triggering an isolated action<br />
*** anything that is not a full sentence<br />
*** current actions in the status bar<br />
** Final period:<br />
*** complete sentences (e.g., explanations and descriptions) that can be chained with other sentences<br />
** Either a period or no period is acceptable (but this should be consistent throughout the game) for:<br />
*** isolated tooltips / small sentences<br />
*** game logs (no period is usually preferable)<br />
*** error messages (unless there is more than one sentence in the error message; final period is mandatory in this case)<br />
<br />
Otherwise, you should try to follow as closely as possible the general style and format (including capitalization) used in the published English rulebook and game materials.<br />
<br />
== Focus on translating notifications ==<br />
<br />
Usually, translating a website is simple: you just call a function on every string you have to translate, and the string is translated in the player's language. On Board Game Arena, this is exactly the same with the "_( string )" function.<br />
<br />
However, there is one difference on BGA: notifications. The server is sending notifications to players, and most of the time the notifications are the same for every players, no matter what language each player is using. This is why notifications are translated on client side in the proper language, even if the strings are defined on server side.<br />
<br />
== WARNING: Make sure your strings will be translated! ==<br />
<br />
For each game, our translation tool does a full scan of the code, looking for translation markers like "_()" or "clienttranslate()". (See below for the full list of translation markers.)<br />
<br />
If your original string is not completely contained inside one of these markers, it won't be translated.<br />
<br />
<pre><br />
// Examples: the following strings will be translated:<br />
var mystring_translated = _("my string"); // JS<br />
$mystring_translated = self::_("my string"); // PHP<br />
$mystring_translated = sprintf( self::_("my string with an %s argument"), $argument ); // PHP<br />
<br />
// Examples: the following strings WILL NOT be translated:<br />
$my_string = "my string";<br />
$not_translated = self::_( $my_string ); // The original string is not contained within a translator marker => no translation<br />
$not_translated = self::_( sprintf( "my string with a %s argument", $argument ) ); // Ditto<br />
</pre><br />
<br />
== How to not make translators crazy ;) ==<br />
<br />
* Try to reuse the exact same strings (with the same case, etc.) to minimize the number of strings to translate.<br />
** '''Example''': Consider replacing <pre>self::_("Winner")</pre> and <pre>self::_("Winners")</pre> (two strings to translate) with <pre>self::_("Winner(s)")</pre><br />
** '''Example 2''': <pre>clienttranslate("play a card")</pre> and <pre>clienttranslate("Play a card")</pre> means there will be two strings to translate.<br />
* Do not mark as translatable a game element that does not have to be translated (e.g., if the name of a monster on a card is "Zzzzz", maybe there's no need to translate it).<br />
* Words does not come in the same order in each language. Thus, when you have to translate a string with an argument, do not write something like:<br />
<pre>self::_("First part of the string, ").$argument.' '.self::_("second part of the string")</pre><br />
Write instead:<br />
<pre>sprintf( self::_("First part of the string, %s second part of the string"), $argument )</pre><br />
(or the equivalent "dojo.string.substitute" in Javascript)<br />
* When translators are going to translate your game, the most difficult task for them is to get the context of the string to be translated. The shorter the string is, the more difficult the task is for them. As a rule of thumb, try to avoid short, insignificant strings that require knowledge of their surrounding context. You can also leave a comment on the context of the string in the translation program (English to English) if you are the developer of the game.<br />
* The BGA translation policy is to be flexible on grammar. We prefer to write "player gets 1 coin(s)" rather than write two versions of the same string for plural and singular - it reduces the number of strings to translate.<br />
* Instead of writing elaborate strings like "With the effect of ZZZ, player XXX gains a new YYY", which is very difficult to translate, write strings like "ZZZ: XXX gets YYY".<br />
* Use present tense instead of past. E.g., "player gets wood" instead of "player got wood".<br />
* Avoid using gender-specific pronouns. E.g., instead of "player returns card to *his* hand" use "player returns card to *their* hand," or just avoid using pronouns. E.g., "player picks up the card".<br />
<br />
== On client side (Javascript) ==<br />
<br />
On client side, things are quite simple: you just have to use the "_()" function for all strings you want to translate.<br />
<br />
Examples:<br />
<pre><br />
// Get a string in player's language:<br />
var translated = _("original english string");<br />
<br />
// Get a string in player's language with parameter:<br />
var translated = dojo.string.substitute( _("You can pick ${p} cards and discard ${d}"), {<br />
p: 2,<br />
d: 4<br />
} );<br />
</pre><br />
<br />
'''WARNING:''' in Javascript strings to translate, you should never use '\n', '\t' or such, as it will break the translation bundle and result in all the Javascript translation to fail. In any case, the strings will result in HTML code, and such character codes won't have any impact on the HTML rendering. You should use HTML markup instead.<br />
<br />
'''ANOTHER WARNING:''' you cannot use this function _() in the javascript object constructor, but you can achieve the same if you use it the setup method<br />
<br />
== On server side (PHP) ==<br />
<br />
On PHP side, you can use 3 different functions to specify that a string must be translated.<br />
<br />
'''clienttranslate( "string to translate" ):'''<br />
<br />
This function is ''transparent'': it will return the original English string without any change. Its only purpose is to mark this string as "must be translated", and to make sure the translated version of the string will be available on client side.<br />
<br />
In general, you use clienttranslate:<br />
* In your '''states.inc.php''', for the fields "description" and "descriptionmyturn".<br />
<br />
<pre><br />
"description" => clienttranslate('${card_name}: ${actplayer} must discard 4 identical energies'),<br />
</pre><br />
<br />
* In '''material.inc.php''', when defining text for game materials that must be displayed on the client side.<br />
<br />
<pre><br />
$this->card_types = array(<br />
<br />
1 => array(<br />
'name' => clienttranslate("Amulet of Air"), // Thus, we can use "_( card_name )" on Javascript side.<br />
</pre><br />
<br />
* When sending a notification with ''notifyAllPlayers'' or ''notifyPlayer'', in the game log string and all game log arguments that need a translation.<br />
<br />
<pre><br />
// A game log string with no argument:<br />
self::notifyAllPlayers( 'pickLibraryCards', clienttranslate("Everyone draw cards from their library"), array() );<br />
</pre><br />
<br />
As a consequence there is no point passing variables to this function. E.g.:<br />
<br />
<pre><br />
notif="foo";<br />
self::notifyAllPlayers( 'log', clienttranslate(notif)); // BAD<br />
<br />
notif=clienttranslate("foo");<br />
self::notifyAllPlayers( 'log', notif); // GOOD<br />
</pre><br />
<br />
Translating arguments is a little bit more complex. This uses the '''i18n''' special argument as below:<br />
<br />
<pre><br />
// In the following example, we translate the game log itself, but also the "card_name" argument:<br />
<br />
self::notifyAllPlayers( 'winPoints', clienttranslate('${card_name}: ${player_name} gains ${points} point(s)'), array(<br />
'i18n' => array( 'card_name' ), // <===== We specify here that "card_name" argument must be translated<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'points' => $points,<br />
'card_name' => $this->card_types[8]['name'] // <==== Here, we provide original English string.<br />
) ); <br />
</pre><br />
<br />
To ensure the translation of the i18n argument will be made, clienttranslate must have been used somewhere, for instance:<br />
<br />
<pre><br />
$this->card_types = array(<br />
...<br />
8 => array(<br />
'name' => clienttranslate("Amulet of Fire"),<br />
...<br />
</pre><br />
<br />
Pay attention when using the '''i18n''' argument when translating arguments for the client: do NOT use same argument for both translations AND key codes for client-side actions (like using 'card_name' to move it on the player board as described in the example). It's pretty obvious in the example, but it can be very tricky when translation is made at the end of the development (which is often the case). Use explicit argument names like 'card_name_translated' by example.<br />
<br />
'''WARNING:''' you should NEVER use concatenation with ''clienttranslate'', as it would result in a different string to translate at runtime than the one retrieved statically in the translation system, and so translation would not be applied. If you need to compose a string, use substitution and the i18n parameter (but you also have to pay attention not to compose sentences in a way that is dependent upon the English language specific syntax, or it may be impossible to translate correctly in another language: sometimes, you need multiple full sentences rather than relying on substitution).<br />
<br />
'''self::_( "my string to translate" ):'''<br />
<br />
This function returns a string translated in the language of CURRENT user (i.e. player who send the request to the server) (be careful, this is NOT the active player).<br />
<br />
Most of the time, you don't need to translate strings on server side, except on the following 3 situations:<br />
* When throwing an exception because the player did a forbidden move.<br />
<br />
<pre><br />
// This will display a translatable red message to the player that just did some wrong action:<br />
throw new BgaUserException( self::_('You must choose 3 cards') );<br />
<br />
// ... notice the use of BgaUserException that signals that this exception is "expected". In theory, all exception that are expected should be translated.<br />
</pre><br />
<br />
* In "yourgame.view.php", when creating the labels for the game interface used in your template (.tpl) file.<br />
<br />
<pre><br />
$this->tpl['CARDS_FOR_YEAR_2'] = self::_("Your cards for year II");<br />
</pre><br />
<br />
'''Nota bene:''' it is recommended to set translated text in your interface client side rather than use template variables and server translation, so that translation works natively in replays (that don't have server side translations). For simple strings, the framework will handle moving the translation client side, but for more complex strings it may not work. In particular, you should not use server side translation for templates with substitution variables (or use the specific function "gameview_str_replace( $search, $replace, $string )" if not possible otherwise or for old code compatibility). Also, you should never use a "to_translate" class in your .tpl as it is used internally by the framework.<br />
<br />
* Eventually, in your material.inc.php, if for example you need to use some string elements in your exceptions.<br />
<br />
<pre><br />
// In material.inc.php, $this->energies[n]['nametr'] has been created with the self::_() method. Now we can do this:<br />
throw new BgaUserException( self::_("To execute this action you need more: ").' '.$this->energies[$resource_id]['nametr'] );<br />
</pre><br />
<br />
* Eventually, in your "getAllDatas" PHP method, as the data return by this method is used only by current user.<br />
<br />
'''totranslate( "my string to translate" ):'''<br />
<br />
This function works exactly like 'clienttranslate', except it tells BGA that the string is not needed on client side.<br />
<br />
You should not use this function, except on the following cases:<br />
* Statistics name in stats.inc.php<br />
* Option names and option values name in gameoptions.inc.php<br />
<br />
== On server side - advanced (PHP) ==<br />
<br />
If you want to use translation system in your custom static modules, you will first need to expose the _() function :<br />
<pre><br />
class mygame extends Table {<br />
// Exposing protected method translation<br />
public static function totranslate($text) {<br />
return self::_($text);<br />
}<br />
}<br />
</pre><br />
<br />
Then you'll be able to use this directly in other modules by doing<br />
<pre><br />
throw new BgaUserException(mygame::totranslate("Translated error from my awesome module file"));<br />
</pre><br />
<br />
Notice how the "totranslate" is also used by the static analysis to detect your string. So the following will not work :<br />
<pre><br />
$msg = "Translated error from my awesome module file";<br />
throw new BgaUserException(mygame::totranslate($msg));<br />
</pre></div>
JonChambers
http://en.doc.boardgamearena.com/index.php?title=Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl&diff=8284
Game layout: view and template: yourgamename.view.php and yourgamename yourgamename.tpl
2021-05-17T10:19:16Z
<p>JonChambers: /* Overview */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Overview ==<br />
[[File:bga-pages-from-templates.PNG|700px]]<br />
<br />
There are two files involved in overall game layout: yourgamename.view.php and yourgamename_yourgamename.tpl.<br />
<br />
These files work together to provide the HTML layout of your game.<br />
<br />
Using these 2 files, you specify what HTML is rendered in your game client interface.<br />
<br />
In .tpl file you can directly write raw HTML that will be displayed by the browser.<br />
<br />
Example: extract of "hearts_hearts.tpl":<br />
<br />
<pre><br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>{MY_HAND}</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
Things is curly braces are template variables. You cannot put any engish text directly there.<br />
<br />
Your view and your template are supposed to generate only the BASE layout of the game.<br />
<br />
You shouldn't try to setup the current game situation in the view: this is the role of your Javascript code. Why? Because you'll have to write Javascript code to put game elements in place anyway, and you don't want to write it twice :)<br />
<br />
Example of things to generate in your view:<br />
* The overall layout of your game interface (what is displayed where).<br />
* The board and fixed elements on the board (ex: places for cards, squares, ...).<br />
* The tokens which are always on the board (but JS code may move them around during setup)<br />
<br />
Example of things that shouldn't be generate by your view:<br />
* Game elements that come and go from the game area.<br />
* Game elements that normally hidden from players (other players cards, cards in the deck).<br />
<br />
== Template system ==<br />
<br />
BGA is using the phplib template system, used for example in PHPbb forums.<br />
<br />
More details about how to use phplib template system here:<br />
https://web.archive.org/web/20170506065401/http://www.phpbuilder.com:80/columns/david20000512.php3<br />
<br />
== Variables ==<br />
<br />
In your template (.tpl) file, you can use variables. Then in your view (.view.php) file, you fill these variables with values.<br />
<br />
In the example above, "{MY_HAND}" is a variable. As you can see, a variable is uppercase characters surrounded by "{" and "}".<br />
<br />
This is example of how to assign values to these variables in .view.php:<br />
<br />
Examples:<br />
<pre><br />
<br />
// Display a translated version of "My hand" at the place of the variable in the template<br />
$this->tpl['MY_HAND'] = self::_("My hand");<br />
<br />
// Display some raw HTML material at the place of the variable<br />
$this->tpl['MY_HAND'] = self::raw( "<div class='myhand_icon'></div>" );<br />
<br />
</pre><br />
<br />
WARNING: do not use a variable called {id} as it will interfere with action buttons.<br />
<br />
WARNING: do not use a variable called {LB_[whatever]} as any variable starting with LB_ will interfere with the translation system.<br />
<br />
== Template Blocks ==<br />
<br />
Using "blocks", you can repeat a piece of HTML from your template several time.<br />
<br />
You should use "blocks" whenever you have a block of HTML that must be repeated many times. For example, for ''Reversi'', we have to generate 64 (8x8) squares:<br />
<br />
<pre><br />
<br />
(in reversi_reversi.tpl)<br />
<br />
<div id="board"><br />
<!-- BEGIN square --><br />
<div id="square_{X}_{Y}" class="square" style="left: {LEFT}px; top: {TOP}px;"></div><br />
<!-- END square --><br />
<br />
<div id="discs"><br />
</div><br />
</div><br />
<br />
(in reversi.view.php)<br />
<br />
$this->page->begin_block( "reversi_reversi", "square" );<br />
<br />
$hor_scale = 64.8;<br />
$ver_scale = 64.4;<br />
for( $x=1; $x<=8; $x++ ) {<br />
for( $y=1; $y<=8; $y++ ) {<br />
$this->page->insert_block( "square", array(<br />
'X' => $x,<br />
'Y' => $y,<br />
'LEFT' => round( ($x-1)*$hor_scale+10 ),<br />
'TOP' => round( ($y-1)*$ver_scale+7 )<br />
) );<br />
} <br />
}<br />
<br />
</pre><br />
<br />
Explanations:<br />
* You specify a block in your template file, using "BEGIN" and "END" keywords as xml comment. In the example above, we are creating a block named "square".<br />
* In your view, you declare your block using "begin_block" method.<br />
* Then, you can insert as many block as you want to, using "insert_block" method.<br />
<br />
The insert_block method takes 2 parameters:<br />
* the name of the block to insert.<br />
* an associative array you can use to assign values to template variables of this block. In the example above, there are 4 parameters in the block (X, Y, LEFT and TOP).<br />
<br />
== Nested blocks ==<br />
<br />
You can use nested blocks. In the example below, we are going to add a mini-board for each player of the game, with 4 card places on each of it:<br />
<br />
<pre><br />
<br />
(In template file)<br />
<br />
<!-- BEGIN player --><br />
<div class="miniboard" id="miniboard_{PLAYER_ID}"><br />
<br />
<div class="card_places"><br />
<!-- BEGIN card_place --><br />
<div id="card_place_{PLAYER_ID}_{PLACE_ID}"><br />
</div><br />
<!-- END card_place --><br />
</div><br />
<br />
</div><br />
<!-- END player --><br />
<br />
(In view file)<br />
<br />
$this->page->begin_block( "mygame_mygame.tpl", "card_place" ); // Nested block must be declared first<br />
$this->page->begin_block( "mygame_mygame.tpl", "player" );<br />
<br />
foreach( $players as $player_id => $player ) {<br />
// Important: nested block must be reset here, otherwise the second player miniboard will<br />
// have 8 card_place, the third will have 12 card_place, and so one...<br />
$this->page->reset_subblocks( 'card_place' ); <br />
<br />
for( $i=1; $i<=4; $i++ ) {<br />
$this->page->insert_block( "card_place", array( <br />
'PLAYER_ID' => $player_id,<br />
'PLACE_ID' => $i<br />
)<br />
);<br />
}<br />
<br />
$this->page->insert_block( 'player', array( 'PLAYER_ID' => $player_id );<br />
}<br />
<br />
</pre><br />
<br />
== Javascript templates ==<br />
<br />
For game elements that come and go from the game area, we suggest you to define a Javascript template.<br />
<br />
These will allow to separate all html from javascript and php files and keep it strictly in template file.<br />
<br />
A Javascript template is defined in your template file like this:<br />
<br />
(Reversi Token from Reversi example):<br />
<pre><br />
<script type="text/javascript"><br />
<br />
// Templates<br />
<br />
var jstpl_disc='<div class="disc disccolor_${color}" id="disc_${xy}"></div>';<br />
<br />
</script> <br />
</pre><br />
<br />
Note: a section for javascript templates is already available at the end of your template skeleton file.<br />
<br />
Then, you can use this javascript template to insert this piece of HTML in your game interface, like this:<br />
<br />
<pre><br />
dojo.place( this.format_block( 'jstpl_disc', {<br />
xy: x+''+y,<br />
color: color<br />
} ) , 'discs' );<br />
</pre><br />
<br />
WARNING: always use lowercase for substitution variables in your Javascript templates, in order to avoid collision with phplib template variables (in particular, do not use ${ID}).<br />
<br />
Note that '''you must translate''' any text arguments passed to ''this.format_block()'' that will be rendered on the screen, for example [[Translations#On_client_side_.28Javascript.29|using "_()"]].<br />
<br />
== How to access game information from .view.php ==<br />
<br />
From your .view.php, you can access the following:<br />
<br />
=== Access current player id===<br />
<br />
<pre><br />
global $g_user;<br />
$current_player_id = $g_user->get_id();<br />
</pre><br />
<br />
=== Access game object ===<br />
<br />
In your view file, "$this->game" contains an instance of your main game class.<br />
<br />
Example:<br />
<pre><br />
<br />
// Access to some game elements description described in your "material.inc.php":<br />
$my_cards_types = $this->game->card_types;<br />
<br />
// Access to any (public) method defined in my .game.php file:<br />
$result = $this->game->myMethod();<br />
</pre><br />
<br />
<br />
=== Access spectator status ===<br />
<br />
For spectators (players who are not part of the game but just spectating it) you must be careful of displaying all public information, but no private information.<br />
<br />
In your view file, you can use $this->game->isSpectator() to know if the player is not part of the game, and adapt the interface to this case.</div>
JonChambers