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; } fillRect(x,y,x2,y2,r) { //This function allows using new rounded corners on newer firmware if(process.env.VERSION === "2v12" || process.env.VERSION === "2v11" || process.env.VERSION === "2v10") { g.fillRect(x,y,x2,y2); } else { g.fillRect({x:x,y:y,x2:x2,y2:y2,r:r}); } } 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]; if(outlined) { g.setColor(0,0,0); this.fillRect(x-1,y-1,x+81,y+101,6); } g.setColor(colorArr); g.setBgColor(colorArr); this.fillRect(x,y,x+80,y+100,6); 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.setBgColor(0,0,0); if(flipped) { g.setColor(0,0,0); this.fillRect(x-1,y+1,x+81,y-101,6); g.setColor(255,255,255); this.fillRect(x,y,x+80,y-100,6); 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.setColor(0,0,0); this.fillRect(x-1,y-1,x+81,y+101,6); g.setColor(255,255,255); this.fillRect(x,y,x+80,y+100,6); g.setColor(0,0,0); 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); this.fillRect(x,y,x+110,y+45,6); 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); this.fillRect(x,y,x+20,y+20,2); 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();}); E.showScroller({ h: 25, c: 10, draw: (idx,r) => { g.setBgColor("#000").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1); g.setColor("#fff"); switch(idx) { case 0: g.setFont("6x8:2").drawString("Rules can be",r.x+10,r.y+4); break; case 1: g.setFont("6x8:2").drawString("found on",r.x+10,r.y+4); break; case 2: g.setFont("Vector:18").drawString("asmadigames.com",r.x+10,r.y+4); break; case 3: g.setFont("6x8:1").drawString("Use button to show menu.",r.x+10,r.y+4); break; case 4: g.setFont("6x8:1").drawString("Swipe L/R for hand/palette.",r.x+10,r.y+4); break; case 5: g.setFont("6x8:1").drawString("Tap card to see details.",r.x+10,r.y+4); break; case 6: g.setFont("6x8:1").drawString("Swipe card L/R to play.",r.x+10,r.y+4); break; case 7: g.setFont("6x8:1").drawString("Finish turn in menu.",r.x+10,r.y+4); break; case 9: g.fillRect(r.x+40,r.y+0,r.x+140,r.y+20); g.setColor(0,0,0); g.setFont("Vector:14").drawString("OK",r.x+80,r.y+4); break; } }, select: (idx) => { if(idx === 9){ E.showScroller(); drawMainMenu(); } } }); } function drawGameOver(win) { startedGame = false; E.showAlert(win ? "AI has given up. You Win!" : "You cannot play. AI wins.").then(function(){ 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)) { drawGameOver(false); } 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});