Skip to content

Commit

Permalink
add multicard drag to solitaire
Browse files Browse the repository at this point in the history
  • Loading branch information
ryaanahmed committed Sep 6, 2023
1 parent 48e338e commit 0aad6d8
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 104 deletions.
8 changes: 7 additions & 1 deletion assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,13 @@
"nextButton": "Curious about the source of this reading? Click here."
},
"screen4": {
"p": "While our team has created the program that randomized the cards drawn from Alliette's cartomancy deck and assigned these cards values based on Alliette's writings, the interpretation of these values you have just read was generated by ChatGPT.",
"title": "An Explanation of Your Reading",
"p0": "The three or five cards that our online cartomancer has interpreted were selected randomly from the 33-card deck designed by Alliette before the Revolution. Note that all the cards in the deck can be dealt upside-down or right-side up. Alliette provided different values for each card depending on its orientation; this feature in turn is one of several rules that govern the values generated by the relationships between the cards.",
"p1": "Here are three other Alliette rules that structure the values of the cards:",
"li0": "If the \"Etteilla\" card, or wild card, is turned over, the value on the card to its left becomes that associated with the letter \"E\" on the top of the card.",
"li1": "Each card has a value between 1 and 31. If any two cards together add up to 31, the value of the card becomes that next to the letter \"R\" at the top of the card.",
"li2": "If there are two or more of one card value, e.g., two kings, or three 7’s, the game uses the values on the right-hand side of the card to add an additional predictive element to the mix.",
"p2": "While our team has created the program that randomized the cards drawn from Alliette's cartomancy deck and assigned these cards values based on these rules, the interpretation of these values provided by our game is generated by a nuanced prompt we have submitted to ChatGPT 3.5.",
"nextButton": "Try again!",
"moreInfo": "Learn about the history of Tarot cards"
}
Expand Down
7 changes: 2 additions & 5 deletions backend/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,8 @@ def divination_card_request(request):
def generate_prediction(request):
question = json.loads(request.body.decode('utf-8')).get("question", None)

# Grab api key from file
api_key_file = os.path.join(settings.PROJECT_ROOT, "openai_key")
with open(api_key_file, 'r', encoding='utf-8') as file:
openai_key = file.readline().strip()
openai.api_key = openai_key
openai.api_key = settings.OPENAI_KEY


if question:
completion = openai.ChatCompletion.create(
Expand Down
4 changes: 4 additions & 0 deletions backend/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
DB_PATH = os.path.join(BACKEND_DIR, 'db.sqlite3')
PROJECT_ROOT = os.path.dirname(BACKEND_DIR)

OPENAI_API_KEY_PATH = os.path.join(PROJECT_ROOT, "openai_key")
with open(OPENAI_API_KEY_PATH, 'r', encoding='utf-8') as file:
OPENAI_KEY = file.readline().strip()

# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/


Expand Down
12 changes: 10 additions & 2 deletions frontend/components/DivinationGame.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,15 @@ const EndingScreen = ({ goToNext }) => {
<>
<Row>
<Col>
<p>{t("divination.screen4.p")}</p>
<h2>{t("divination.screen4.title")}</h2>
<p>{t("divination.screen4.p0")}</p>
<p>{t("divination.screen4.p1")}</p>
<ul>
<li>{t("divination.screen4.li0")}</li>
<li>{t("divination.screen4.li1")}</li>
<li>{t("divination.screen4.li2")}</li>
</ul>
<p>{t("divination.screen4.p2")}</p>
</Col>
</Row>

Expand All @@ -256,7 +264,7 @@ const EndingScreen = ({ goToNext }) => {
<a className="btn btn-outline-dark" onClick={goToNext}>{t("divination.screen4.nextButton")}</a>
</Col>
<Col xs="auto">
<a className="btn btn-outline-dark" onClick={goToNext}>{t("divination.screen4.moreInfo")}</a>
<a className="btn btn-outline-dark" href="/tarot">{t("divination.screen4.moreInfo")}</a>
</Col>
</Row>
</>
Expand Down
22 changes: 16 additions & 6 deletions frontend/components/solitaire/FoundationStack.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import SolitaireCard from './SolitaireCard';

const FoundationStack = ({ stack, handleFoundationDrop, suit, deck }) => {
const FoundationStack = ({ stack, handleFoundationDrop, suit, deck, dragState, setDragState }) => {
let cardData;

if (stack.length === 0) {
Expand All @@ -10,14 +10,26 @@ const FoundationStack = ({ stack, handleFoundationDrop, suit, deck }) => {
cardData = stack[stack.length-1];
}

const stackName = `foundation${suit}`;
// Foundation cards are going to have cardIndex undefined
// so they only fire a -1 here for dragEnd and mouseUp
const setDragCardIndex = (cardIndex) => {
if (cardIndex === -1) {
setDragState('', -1);
} else {
setDragState(stackName, -1);
}
};

const card = <SolitaireCard
card={cardData.card}
suit={suit}
deck={deck}
faceUp={true}
active={false}
draggable={true}
stack={`foundation${suit}`}
draggable={stack.length > 0}
setDragCardIndex={setDragCardIndex}
transparent={dragState.stackName === stackName}
/>;

const className =
Expand All @@ -28,9 +40,7 @@ const FoundationStack = ({ stack, handleFoundationDrop, suit, deck }) => {

const handleDrop = (e) => {
e.preventDefault();
const draggedCardData = JSON.parse(e.dataTransfer.getData('application/json'));
console.log(draggedCardData);
handleFoundationDrop(suit, draggedCardData);
handleFoundationDrop(suit);
};

// required to allow dropping
Expand Down
14 changes: 7 additions & 7 deletions frontend/components/solitaire/SolitaireCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ on whether the card is face up or face down
Fetches the correct card image from the Soliatire img directory `/static/img/games/solitaire/`
*/

const SolitaireCard = ({card, suit, deck, faceUp, draggable, stack}) => {
const SolitaireCard = ({
index, card, suit, deck, faceUp, draggable, setDragCardIndex, transparent
}) => {
let imgSrc = "/static/img/games/solitaire/blue-back.jpeg";
if (faceUp) {
if (["K", "Q", "J"].indexOf(card) === -1) {
Expand All @@ -27,16 +29,14 @@ const SolitaireCard = ({card, suit, deck, faceUp, draggable, stack}) => {
}
}

const handleDragStart = (e) => {
e.dataTransfer.setData('application/json', JSON.stringify({ srcStack: stack }));
e.dataTransfer.effectAllowed = "move";
};

return <img
className='game-card'
src={imgSrc}
draggable={draggable}
onDragStart={handleDragStart}
style={transparent ? { opacity: 0.5 } : {}}
onDragStart={() => setDragCardIndex(index)}
onMouseUp={() => setDragCardIndex(-1)}
onDragEnd={() => setDragCardIndex(-1)}
/>;
};

Expand Down
133 changes: 64 additions & 69 deletions frontend/components/solitaire/SolitaireGame.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,23 @@ class SolitaireGame extends React.Component {
tableau6: t6, // initial size 6
tableau7: t7, // initial size 7
stock: stock, // all cards in the stock will be face down
waste: [] // all cards in the waste will be face up, but remember only the most recently added card (at the end of the array) is accessible
waste: [], // all cards in the waste will be face up, but remember only the most recently added card (at the end of the array) is accessible
dragState: {
stackName: '',
cardIndex: -1,
}
};
};

setDragState = (stackName, cardIndex) => {
console.log("Setting drag state: ", stackName, cardIndex);
this.setState(
{dragState: {stackName, cardIndex }}
);
};

clearDragState = () => { console.log("Clearing drag state"); this.setDragState('', -1); };

/*
Only call this when the stock stack is clicked
Draws a card from the stock stack and puts it in the waste stack (up to the user whether or not to leave it in the waste
Expand All @@ -165,43 +178,39 @@ class SolitaireGame extends React.Component {
});
};

/*
Handle a tableau stack being clicked: when a tableau gets clicked on, there are two possibilities:
(1) the stack clicked contains a card that will be moved, or (2) the stack clicked will get a card moved to it.
The argument `stackName` is a string
Note (from Alyssa): when the 2-click transfer method is changed to be a click-and-drag method, this function should allow
for multiple cards to be moved at once, e.g. moving a chunk of cards with values 6,5,4 from stack A to stack B that has a card with value 7 on top)
*/
handleTableauDrop = (tableauIndex, draggedCardData) => {
const destStackName = 'tableau' + tableauIndex.toString();
const srcStackName = draggedCardData.srcStack;
handleTableauDrop = (destTableauIndex) => {
const destStackName = 'tableau' + destTableauIndex.toString();
const srcStackName = this.state.dragState.stackName;
const srcStack = this.state[srcStackName];
const destStack = this.state[destStackName];
const transferCard = srcStack.pop();

const transferCard =
srcStackName.startsWith('tableau')
? srcStack[this.state.dragState.cardIndex]
: srcStack[srcStack.length-1];

this.clearDragState();

if (srcStack === destStack) {
srcStack.push(transferCard);
// If source and destination stacks are the same, no further action needed
return;
}

// Only kings go on empty stacks

let isValidTransfer = false;
if (destStack.length === 0) {
if (transferCard["card"] === "K") {
destStack.push(transferCard);
} else {
srcStack.push(transferCard);
return;
}
// Only kings go on empty stacks
isValidTransfer = transferCard["card"] === "K";
} else {
// Stack isn't empty, so check stacking rules
const destStackTopCard = destStack[destStack.length-1];
if (!(compareCards(destStackTopCard["card"], transferCard["card"]) &&
compareSuits(destStackTopCard["suit"], transferCard["suit"]))) {
srcStack.push(transferCard);
return;
}
isValidTransfer = compareCards(destStackTopCard["card"], transferCard["card"]) &&
compareSuits(destStackTopCard["suit"], transferCard["suit"]);
}

destStack.push(transferCard);
if (isValidTransfer) {
const cardsToTransfer = srcStack.splice(this.state.dragState.cardIndex);
destStack.push(...cardsToTransfer);
}

if (srcStack.length > 0) {
Expand All @@ -218,42 +227,42 @@ class SolitaireGame extends React.Component {
// For testing purposes
// console.log(stackName + " gained a card");
// console.log(stackName + ": " + this.state[stackName]);
// console.log("activeStack set to 0");
// console.log("activeStack: " + this.state.activeStack);
// console.log(this.state);

};

/*
Handle a foundation stack being clicked: when a foundation gets clicked on, there are two possibilities:
(1) the stack clicked contains a card that will be moved, or (2) the stack clicked will get a card moved to it.
*/
handleFoundationDrop = (suit, draggedCardData) => {
const destStackName = 'foundation' + suit;
const srcStackName = draggedCardData.srcStack;

handleFoundationDrop = (suit) => {
const srcStackName = this.state.dragState.stackName;
const srcStack = this.state[srcStackName];

// can't drop multiple cards on to a foundation
if (srcStack.length-1 !== this.state.dragState.cardIndex) return;

const destStackName = 'foundation' + suit;
const destStack = this.state[destStackName];
const transferCard = srcStack.pop();

this.clearDragState();

if (srcStack === destStack) {
srcStack.push(transferCard);
return;
}

// Card must be same suit as foundation
if (transferCard["suit"] !== suit) {
srcStack.push(transferCard);
return;
}
// Card must be same suit as foundation
if (transferCard["suit"] !== suit) {
srcStack.push(transferCard);
return;
}

if (destStack.length === 0) {
// Aces can only go on empty stacks
if (transferCard["card"] === "A") {
destStack.push(transferCard);
} else {
srcStack.push(transferCard);
return;
}
if (destStack.length === 0) {
// Aces can only go on empty stacks
if (transferCard["card"] === "A") {
destStack.push(transferCard);
} else {
srcStack.push(transferCard);
return;
}
} else {
const destStackTopCard = destStack[destStack.length-1];
if (!compareCards(transferCard["card"], destStackTopCard["card"])) {
Expand Down Expand Up @@ -295,29 +304,10 @@ class SolitaireGame extends React.Component {
stacks["waste"] = waste;

this.setState({
activeStack: 0,
stacks: stacks
});
};

/*
Handle the waste pile being clicked
Opposite of logic for handleFoundationDrop: user can move a card FROM waste, but can't move a card TO waste
*/
handleWasteClick = () => {
// console.log("Clicking waste stack");

// If `this.state.activeStack` is null, make the given `stackName` the activeStack.
if (this.state.activeStack === 0) {
this.setState({
activeStack: "waste"
});
return;
}

// Otherwise, `this.state.activeStack` is not null, so do nothing
};

/*
If the stockpile becomes empty and the game isn't over, refresh the stock with cards from the waste.
This will get called when the user clicks on the 'refresh stock' button
Expand Down Expand Up @@ -370,6 +360,8 @@ class SolitaireGame extends React.Component {
index={i}
key={i}
deck={deck}
dragState={this.state.dragState}
setDragState={this.setDragState}
/>
);
}
Expand All @@ -381,6 +373,8 @@ class SolitaireGame extends React.Component {
suit={suit}
key={suit}
deck={deck}
dragState={this.state.dragState}
setDragState={this.setDragState}
/>
);
});
Expand All @@ -397,7 +391,8 @@ class SolitaireGame extends React.Component {
handleWasteClick={this.handleWasteClick}
refreshStock={this.refreshStock}
deck={deck}
wasteActive={this.state.activeStack === "waste"}
dragState={this.state.dragState}
setDragState={this.setDragState}
/>
</Col>
<Col md={8} className='foundations justify-content-end'>
Expand Down
20 changes: 16 additions & 4 deletions frontend/components/solitaire/StockWaste.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@ const StockWaste = ({
handleStockClick,
handleWasteClick,
refreshStock,
wasteActive,
dragState,
setDragState,
deck
}) => {
const stockCard = stock.length > 0
? <img onClick={handleStockClick} className="game-card" src='/static/img/games/solitaire/blue-back.jpeg'/>
: <button className="btn btn-outline-dark mr-4"
onClick={refreshStock}>Refresh <br/>Stock</button>;
: <button className="btn btn-outline-dark"
onClick={refreshStock}></button>;

// Waste cards are going to have cardIndex undefined
// so they only fire a -1 here for dragEnd and mouseUp
const setDragCardIndex = (cardIndex) => {
if (cardIndex === -1) {
setDragState('', -1);
} else {
setDragState('waste', -1);
}
};

let wasteCard;
if (waste.length > 0) {
Expand All @@ -23,8 +34,9 @@ const StockWaste = ({
suit={wasteCardData.suit}
deck={deck}
faceUp={true}
active={wasteActive}
setDragCardIndex={setDragCardIndex}
stack="waste"
transparent={dragState.stackName === "waste"}
/>;
} else {
wasteCard = <img className="game-card" src="/static/img/games/solitaire/stack-placeholder.png" />;
Expand Down
Loading

0 comments on commit 0aad6d8

Please sign in to comment.