Merge pull request #1420 from eyecreate/red7game

Add Red 7 game
pull/1424/head^2^2
Gordon Williams 2022-02-11 08:22:58 +00:00 committed by GitHub
commit e0161d07d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 815 additions and 0 deletions

1
apps/red7game/ChangeLog Normal file
View File

@ -0,0 +1 @@
1.0: Initial version of game

16
apps/red7game/README.md Normal file
View File

@ -0,0 +1,16 @@
# Red 7 Card Game
Play the card game Red 7 on your Bangle.js.
![Screenshot](screenshot.png)
For rules, see [here](https://asmadigames.com/Red7Rules.pdf).
## Usage
Current rule card is shown in center of screen when viewing your hand.
Swipe left to see your palettes and right on the palette screen to go back to your hand. Tap on a card to see it's details and then swipe either left or right to play the card as a rule or a palette card.
Press the watch button to bring up the menu, which you can undo your card plays, end your turn, or start a new game.
## Creator
[Kevin Whitaker](https://www.eyecreate.org)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A6gAAZFykrqwAXlYwTF4PXAC4v/F/4v/F/4v75O7ABfJF8O0ABfCF8PCABG7F4JfhABXCF4LvsRw4vmZIXQF9YuB3YJGF8heJF8u7LxAvkLwXJF9ZeKF8fQLxQvjLwO0BhIvhLwXCF9fCF4PQF9ReNF8JeNF8IuB3b8LF7/JF4PJF9ZeOF75ePF7/QAAIuMd8IAOF/4v/F/4vWwIAXF6gwBADIuTAH4A/AH4Al"))

BIN
apps/red7game/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

View File

@ -0,0 +1,16 @@
{ "id": "red7game",
"name": "Red 7 Card Game",
"shortName" : "Red 7",
"icon": "icon.png",
"version":"1.0",
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
"tags": "game",
"supports":["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
"storage": [
{"name":"red7game.app.js","url":"red7.js"},
{"name":"red7game.img","url":"app-icon.js","evaluate":true}
]
}

781
apps/red7game/red7.js Normal file
View File

@ -0,0 +1,781 @@
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
const colorRank = {"red":6, "orange":5, "yellow":4, "green":3, "blue":2, "indigo":1, "violet":0};
const colorsHex = ["#b01f26", "#d45727", "#cfb82e", "#309c47", "#36aeac", "#2c3a93", "#784298"];
const colorsRules = ["high-\nest\n card", "most\none #", "most\none\ncolor", "most\nevens", "most\nunique\ncolors","most\nin a\nrow", "most\n< 4"];
const numbers = [1,2,3,4,5,6,7];
const handPos = [0,24,24*2,24*3,24*4,24*5,24*6];
function pointRectangleIntersection(p, r) {
return p.x > r.x1 && p.x < r.x2 && p.y > r.y1 && p.y < r.y2;
}
class Card {
constructor(cardNum, cardColor) {
this.cardNum = cardNum;
this.cardColor = cardColor;
this.selected = false;
//this.rect = {};
this.clippedRect = {};
}
get number() {
return this.cardNum;
}
get color() {
return this.cardColor;
}
set isSelected(sel) {
this.selected = sel;
}
get isSelected() {
return this.isSelected;
}
get fullRect() {
return this.rect;
}
get clipRect() {
return this.clippedRect;
}
draw(x,y,outlined) {
this.rect = {x1: x, x2: x+80, y1: y, y2: y+100};
this.clippedRect = {x1: x, x2: x+24, y1: y, y2: y+100};
var colorIndex = colors.indexOf(this.cardColor);
var colorArr = colorsHex[colorIndex];
var colorRule = colorsRules[colorIndex];
g.setColor(colorArr);
g.setBgColor(colorArr);
g.fillRect(x,y,x+80,y+100);
if(outlined) {
g.setColor(0,0,0);
g.drawRect(x,y,x+80,y+100);
}
g.setColor(255,255,255);
g.setFont("Vector:40");
g.setFontAlign(0,0,0);
//g.drawString(this.cardNum,x+40,y+70,true);
g.setFont("6x8:3");
g.drawString(this.cardNum, x+14, y+14, true);
g.setFont("6x8:2");
g.drawString(colorRule, x+45, y+50, true);
g.flip();
}
drawBack(x,y,flipped) {
this.rect = {x1: x, x2: x+80, y1: y, y2: y-100};
this.clippedRect = {x1: x, x2: x+24, y1: y, y2: y-100};
g.setColor(255,255,255);
g.setBgColor(0,0,0);
if(flipped) {
g.fillRect(x,y,x+80,-100);
g.setColor(0,0,0);
g.drawRect(x,y,x+80,-100);
g.setFontAlign(0,0,2);
g.setColor(255,0,0);
g.setBgColor(255,255,255);
g.setFont("Vector:40");
//g.drawString(7,x+40,y-40,true);
} else {
g.fillRect(x,y,x+80,y+100);
g.setColor(0,0,0);
g.drawRect(x,y,x+80,y+100);
g.setFontAlign(0,0,0);
g.setColor(255,0,0);
g.setBgColor(255,255,255);
g.setFont("Vector:40");
//g.drawString(7,x+40,y+40,true);
}
g.flip();
}
drawRot(x,y) {
this.rect = {x1: x, x2: x+45, y1: y, y2: y+110};
var colorIndex = colors.indexOf(this.cardColor);
var colorArr = colorsHex[colorIndex];
var colorRule = colorsRules[colorIndex];
g.setColor(colorArr);
g.setBgColor(colorArr);
g.fillRect(x,y,x+110,y+45);
g.setColor(255,255,255);
g.setFontAlign(0,0,0);
g.setFont("6x8:2");
g.drawString(colorRule, x+55, y+23, true);
g.flip();
}
drawMicro(x,y) {
this.rect = {x1: x, x2: x+20, y1: y, y2: y+20};
var colorIndex = colors.indexOf(this.cardColor);
var colorArr = colorsHex[colorIndex];
g.setColor(colorArr);
g.setBgColor(colorArr);
g.fillRect(x,y,x+20,y+20);
g.setFontAlign(0,0,0);
g.setFont("6x8:2");
g.setColor(255,255,255);
g.drawString(this.cardNum, x+12, y+12, true);
g.flip();
}
isHigher(card) {
if(this.number > card.number) {
return true;
} else if(this.number === card.number) {
if(colorRank[this.color] > colorRank[card.color]) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
class Hand {
constructor(cards) {
if(typeof cards === "undefined") {
this.hand = [];
} else {
this.hand = cards;
}
}
//Can be single card or array of cards
addCard(card) {
this.hand = this.hand.concat(card);
}
//removes card from hand and returns it
removeCard(card) {
var index = this.hand.indexOf(card);
return this.hand.splice(index,1)[0];
}
get handCards() {
return this.hand;
}
draw(y, outlined) {
var count = 0;
for(let c of this.hand) {
c.draw(handPos[count],y, outlined);
count++;
}
}
drawMicro(x, y) {
var count = 0;
for(let c of this.hand) {
c.drawMicro(x+handPos[count],y);
count++;
}
}
drawBacks(y, flipped) {
var count = 0;
for(let c of this.hand) {
c.drawBack(handPos[count], y, flipped);
count++;
}
}
checkForClick(cord) {
for(let card of this.hand) {
//If last card, you can check the whole rectangle
if(this.hand.indexOf(card) === this.hand.length - 1) {
if(pointRectangleIntersection(cord,card.fullRect)) {
return card;
}
}
else if(pointRectangleIntersection(cord,card.clippedRect)) {
return card;
}
}
}
bestHighestCard() {
if(this.hand.length === 0) {
return undefined;
}
var highestCard = this.hand[0];
this.hand.forEach(function(card){
if(card.isHigher(highestCard)) {
highestCard = card;
}
});
return new Hand(highestCard);
}
allCardsMatchingNumber(number) {
var matchingHand = new Hand();
this.hand.forEach(function(card){
if(card.number === number) {
matchingHand.addCard(card);
}
});
return matchingHand;
}
bestCardsOneNumber() {
if(this.hand.length === 0) {
return undefined;
}
var counts = {'1':0,'2':0,'3':0,'4':0,'5':0,'6':0,'7':0};
this.hand.forEach(function(card){
counts[card.number]++;
});
var highestNumber = '1';
for(let n of Object.keys(counts)) {
if(counts[n] > counts[highestNumber]) {
highestNumber = n;
}
if(counts[n] === counts[highestNumber] && n != highestNumber) {
if(n > highestNumber) {
highestNumber = n;
}
}
}
return this.allCardsMatchingNumber(highestNumber);
}
allCardsMatchingColor(color) {
var matchingHand = new Hand();
this.hand.forEach(function(card) {
if(card.color === color) {
matchingHand.addCard(card);
}
});
return matchingHand;
}
bestCardsOneColor() {
if(this.hand.length === 0) {
return undefined;
}
var counts = {'red':0, 'orange':0, 'yellow':0, 'green':0, 'blue':0, 'indigo':0, 'violet':0};
this.hand.forEach(function(card){
counts[card.color]++;
});
var highestColor = 'red';
for(let n of Object.keys(counts)) {
if(counts[n] > counts[highestColor]) {
highestColor = n;
}
if(counts[n] === counts[highestColor] && n != highestColor && counts[highestColor] > 0) {
var h1 = this.allCardsMatchingColor(n);
var h2 = this.allCardsMatchingColor(highestColor);
if(h1.bestHighestCard().handCards.isHigher(h2.bestHighestCard().handCards)) {
highestColor = n;
}
}
}
return this.allCardsMatchingColor(highestColor);
}
bestEvenCards() {
if(this.hand.length === 0) {
return undefined;
}
var matchingHand = new Hand();
this.hand.forEach(function(card){
if(card.number % 2 === 0) {
matchingHand.addCard(card);
}
});
return matchingHand;
}
bestCardsDiffColors() {
if(this.hand.length === 0) {
return undefined;
}
var cloneHand = new Hand();
for(let c of this.handCards) {
cloneHand.addCard(c);
}
var diffHand = new Hand();
diffHand.addCard(cloneHand.bestHighestCard().handCards);
cloneHand.removeCard(cloneHand.bestHighestCard().handCards);
while(cloneHand.handCards.length > 0) {
var highCard = cloneHand.bestHighestCard().handCards;
var colorExists = false;
diffHand.handCards.forEach(function(card){
if(card.color === highCard.color) {
colorExists = true;
}
});
if(!colorExists) {
diffHand.addCard(highCard);
}
cloneHand.removeCard(highCard);
}
return diffHand;
}
bestRun() {
if(this.hand.length === 0) {
return undefined;
}
var runs = {1:0,2:0,3:0,4:0,5:0,6:0,7:0};
var highestCard = {0:new Card(0,"violet"),1:new Card(0,"violet"),2:new Card(0,"violet"),3:new Card(0,"violet"),4:new Card(0,"violet"),5:new Card(0,"violet"),6:new Card(0,"violet"),7:new Card(0,"violet")};
var hands = {0:new Hand(),1:new Hand(),2:new Hand(),3:new Hand(),4:new Hand(),5:new Hand(),6:new Hand(),7:new Hand()};
for(let start = 1; start < 8; start++) {
//check length of run starting from each number
var currentLen = 0;
var highCard = new Card(0,"violet");
for(let num = start; num < 8; num++) {
var hasNum = false;
var matchingCard = undefined;
this.hand.forEach(function(card){
if(card.number === num) {
hasNum = true;
if(matchingCard != undefined) {
if(card.isHigher(matchingCard)) {
matchingCard = card;
}
} else {
matchingCard = card;
}
}
});
if(hasNum) {
currentLen++;
hands[start].addCard(matchingCard);
highCard = matchingCard;
} else {
break;
}
}
runs[start] = currentLen;
highestCard[start] = highCard;
}
//determine best run
var highestRun = 0;
var highestCount = 0;
for(let n = 1; n < 8; n++) {
if(runs[n] > highestCount) {
highestRun = n;
highestCount = runs[n];
} else if (runs[n] === highestCount) {
if(highestCard[n].isHigher(highestCard[highestRun])) {
highestRun = n;
highestCount = runs[n];
}
}
}
return hands[highestRun];
}
bestCardsBelow4() {
if(this.hand.length === 0) {
return undefined;
}
var matchingHand = new Hand();
this.hand.forEach(function(card){
if(card.number < 4) {
matchingHand.addCard(card);
}
});
return matchingHand;
}
}
function isWinningCombo(ruleCard, palette, otherPalette) {
//The rules of red7 say that you are winning if you match the rule better than anyone else(more cards match rule with highest card in match breaking ties).
switch(ruleCard.color) {
case "red":
if(palette.bestHighestCard().handCards.isHigher(otherPalette.bestHighestCard().handCards)) return true;
break;
case "orange":
var best1 = palette.bestCardsOneNumber();
var best2 = otherPalette.bestCardsOneNumber();
if(best1.handCards.length >= best2.handCards.length) {
if(best1.handCards.length === best2.handCards.length) {
if(best1.bestHighestCard().handCards.isHigher(best2.bestHighestCard().handCards)) {
return true;
}
} else {
return true;
}
}
break;
case "yellow":
var best1 = palette.bestCardsOneColor();
var best2 = otherPalette.bestCardsOneColor();
if(best1.handCards.length >= best2.handCards.length) {
if(best1.handCards.length === best2.handCards.length) {
if(best1.bestHighestCard().handCards.isHigher(best2.bestHighestCard().handCards)) {
return true;
}
} else {
return true;
}
}
break;
case "green":
var best1 = palette.bestEvenCards();
var best2 = otherPalette.bestEvenCards();
if(best1.handCards.length >= best2.handCards.length) {
if(best1.handCards.length === best2.handCards.length) {
if(best1.handCards.length === 0) {
return false;
}
else if(best1.bestHighestCard().handCards.isHigher(best2.bestHighestCard().handCards)) {
return true;
}
} else {
return true;
}
}
break;
case "blue":
var best1 = palette.bestCardsDiffColors();
var best2 = otherPalette.bestCardsDiffColors();
if(best1.handCards.length >= best2.handCards.length) {
if(best1.handCards.length === best2.handCards.length) {
if(best1.bestHighestCard().handCards.isHigher(best2.bestHighestCard().handCards)) {
return true;
}
} else {
return true;
}
}
break;
case "indigo":
var best1 = palette.bestRun();
var best2 = otherPalette.bestRun();
if(best1.handCards.length >= best2.handCards.length) {
if(best1.handCards.length === best2.handCards.length) {
if(best1.bestHighestCard().handCards.isHigher(best2.bestHighestCard().handCards)) {
return true;
}
} else {
return true;
}
}
break;
case "violet":
var best1 = palette.bestCardsBelow4();
var best2 = otherPalette.bestCardsBelow4();
if(best1.handCards.length >= best2.handCards.length) {
if(best1.handCards.length === best2.handCards.length) {
if(best1.handCards.length === 0) {
return false;
}
else if(best1.bestHighestCard().handCards.isHigher(best2.bestHighestCard().handCards)) {
return true;
}
} else {
return true;
}
}
break;
}
return false;
}
function canPlay(hand, palette, otherPalette) {
var clonePalette = new Hand();
for(let c of palette) {
clonePalette.addCard(c);
}
//Check if any card to palette can win first.
for(let c of hand.handCards) {
clonePalette.addCard(c);
if(isWinningCombo(ruleCards.handCards[ruleCards.handCards.length-1],clonePalette, otherPalette)) {
return true;
}
clonePalette.removeCard(c);
}
//Next check for wins with rule change.
for(let c of hand.handCards) {
if(isWinningCombo(c, clonePalette, otherPalette)) {
return true;
} else {
//Check if any palette play can win with rule.
for(let h of hand.handCards) {
if(h === c) {}
else {
clonePalette.addCard(c);
if(isWinningCombo(c, clonePalette, otherPalette)) {
return true;
}
clonePalette.removeCard(c);
}
}
}
}
return false;
}
class AI {
constructor(hand, palette) {
this.hand = hand;
this.palette = palette;
}
takeTurn(ruleStack, otherPalette) {
var clonePalette = new Hand();
for(let c of this.palette) {
clonePalette.addCard(c);
}
//Check if any card to palette can win first.
for(let c of this.hand.handCards) {
clonePalette.addCard(c);
if(isWinningCombo(ruleStack.handCards[ruleStack.handCards.length-1],clonePalette, otherPalette)) {
//Play card that wins
this.palette.addCard(c);
this.hand.removeCard(c);
return true;
}
clonePalette.removeCard(c);
}
//Next check for wins with rule change.
for(let c of this.hand.handCards) {
if(isWinningCombo(c, clonePalette, otherPalette)) {
//Play rule card that wins
ruleStack.addCard(c);
this.hand.removeCard(c);
return true;
} else {
//Check if any palette play can win with rule.
for(let h of this.hand.handCards) {
if(h === c) {}
else {
clonePalette.addCard(c);
if(isWinningCombo(c, clonePalette, otherPalette)) {
ruleStack.addCard(c);
this.hand.removeCard(c);
this.palette.addCard(h);
this.hand.removeCard(h);
return true;
}
clonePalette.removeCard(c);
}
}
}
}
return false;
}
}
function shuffleDeck(deckArray) {
E.srand(Date.now());
deckArray.sort(() => Math.random() - 0.5);
}
var deck = [];
var screen = 1;
var startedGame = false;
var playerHand = new Hand();
var playerPalette = new Hand();
var AIhand = new Hand();
var AIPalette = new Hand();
var ruleCards = new Hand();
var undoStack = [];
var aiPlayer = new AI(AIhand, AIPalette);
function drawScreen1_2(card) {
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
//determine what actions can be taken
var playedRule = false;
var playedPalette = false;
for(let stack of undoStack) {
if(stack.to === ruleCards) {
playedRule = true;
}
if(stack.to === playerPalette) {
playedPalette = true;
}
}
Bangle.on('swipe', function(direction){
if(direction === -1 && !playedRule) {
undoStack.push({from:playerHand,to:ruleCards,card:card});
ruleCards.addCard(card);
playerHand.removeCard(card);
drawScreen1();
}
if(direction === 1 && !playedPalette) {
undoStack.push({from:playerHand,to:playerPalette,card:card});
playerPalette.addCard(card);
playerHand.removeCard(card);
drawScreen1();
}
});
Bangle.on("touch", function(z,cord){
if(!pointRectangleIntersection(cord, card.fullRect)) {
drawScreen1();
}
});
//draw elements
g.setBgColor(0,0,0);
g.clear();
playerHand.draw(130, true);
AIhand.drawBacks(40, true);
card.draw(47,47,true);
g.setColor(255,255,255);
if(!playedRule) g.fillPoly([20,50,20,70,40,70,40,90,20,90,20,110,0,80]);
if(!playedPalette) g.fillPoly([155,50,155,70,135,70,135,90,155,90,155,110,175,80]);
g.setFont("4x6:1");
g.setBgColor(255,255,255);
g.setColor(0,0,0);
if(!playedRule) g.drawString("Rule", 20,80, true);
if(!playedPalette) g.drawString("Palette", 155,80, true);
}
function drawScreen1() {
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
Bangle.on('swipe', function(direction){
if(direction === -1) {
drawScreen2();
screen = 2;
} else if(direction === 1) {
drawScreen1();
screen = 1;
}
});
g.setBgColor(0,0,0);
g.clear();
playerHand.draw(130, true);
Bangle.on("touch", function(z,cord){
var card = playerHand.checkForClick(cord);
if (card !== undefined) {
drawScreen1_2(card);
}
});
AIhand.drawBacks(40, true);
//Draw win indicator
var winning = isWinningCombo(ruleCards.handCards[ruleCards.handCards.length-1], playerPalette, AIPalette);
winning ? g.setColor(0,255,0) : g.setColor(255,0,0);
g.fillEllipse(50,50,130,70);
g.setFont("4x6:2");
g.setFontAlign(0,0,0);
g.setColor(255,255,255);
winning ? g.setBgColor(0,255,0) : g.setBgColor(255,0,0);
g.drawString(winning ? "Winning" : "Losing", 90,60, true);
//Draw current rule
var rules = ruleCards.handCards;
if(rules.length > 0) {
rules[rules.length-1].drawRot(40,80);
}
}
function drawScreen2() {
Bangle.removeAllListeners("touch");
g.setBgColor(0,0,0);
g.clear();
g.setColor(255,255,255);
g.setFont("6x8:2");
g.setFontAlign(0,0,0);
g.drawString("AI Palette",85,40,false);
g.drawString("Your Palette", 85, 130, false);
playerPalette.drawMicro(5,150);
AIPalette.drawMicro(5,0);
}
function drawScreenHelp() {
E.showAlert("Rules can be found on asmadigames.com").then(function(){drawMainMenu();});
}
function drawGameOver(win) {
E.showAlert(win ? "AI has given up. You Win!" : "You cannot play. AI wins.").then(function(){
startedGame = false;
undoStack = [];
drawMainMenu();
});
}
function finishTurn() {
undoStack = [];
//Check if AI has cards
if(AIhand.handCards.length === 0) {
drawGameOver(true);
} else {
var takenTurn = aiPlayer.takeTurn(ruleCards, playerPalette);
//Check if game over conditions met.
if(!takenTurn) {
drawGameOver(true);
} else if(playerHand.handCards.length === 0) {
drawGameOver(false);
} else if(!canPlay(playerHand, playerPalette, AIPalette)) {
console.log("no play");
//drawGameOver(false);
drawScreen1();
} else {
E.showMenu();
drawScreen1();
}
}
}
function resetToNewGame() {
g.setBgColor(0,0,0);
g.clear();
deck = [];
//Fill deck with cards
for(let c of colors) {
for(let n of numbers) {
deck.push(new Card(n,c));
}
}
shuffleDeck(deck);
playerHand = new Hand();
playerHand.addCard(deck.pop());
playerHand.addCard(deck.pop());
playerHand.addCard(deck.pop());
playerHand.addCard(deck.pop());
playerHand.addCard(deck.pop());
playerHand.addCard(deck.pop());
playerHand.addCard(deck.pop());
playerPalette = new Hand();
playerPalette.addCard(deck.pop());
AIhand = new Hand();
AIhand.addCard(deck.pop());
AIhand.addCard(deck.pop());
AIhand.addCard(deck.pop());
AIhand.addCard(deck.pop());
AIhand.addCard(deck.pop());
AIhand.addCard(deck.pop());
AIhand.addCard(deck.pop());
AIPalette = new Hand();
AIPalette.addCard(deck.pop());
ruleCards = new Hand();
ruleCards.addCard(new Card(0,"red"));
undoStack = [];
startedGame = true;
aiPlayer = new AI(AIhand, AIPalette);
//determine first player
if(isWinningCombo(new Card(0,"red"), playerPalette, AIPalette)) {
//AI needs to play first
finishTurn();
} else {
drawScreen1();
}
}
function drawMainMenu() {
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
var menu = {"": {"title":"Red 7"}};
if(startedGame === true) {
menu["Continue"] = function(){
E.showMenu();
drawScreen1();
};
if(undoStack.length > 0) {
menu["Undo Turn"] = function(){
for(let stack of undoStack) {
stack.from.addCard(stack.card);
stack.to.removeCard(stack.card);
}
undoStack = [];
E.showMenu();
drawScreen1();
};
}
if(isWinningCombo(ruleCards.handCards[ruleCards.handCards.length-1], playerPalette, AIPalette)) {
menu["Finish Turn"] = function(){
finishTurn();
};
}
}
menu["New Game"] = function() {
E.showMenu();
resetToNewGame();
};
menu["Help"] = function() {
drawScreenHelp();
};
menu["Exit"] = function() {
E.showMenu();
setTimeout(load,300);
};
E.showMenu(menu);
}
drawMainMenu();
setWatch(function(){
drawMainMenu();
},BTN, {edge: "rising", debounce: 50, repeat: true});

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB