Code Sketch
Set
By: windiana
Category: Programming
def shapeRaute() : Picture = { scale(2,1) * rot(45) * trans(-7,-7) -> Picture.rectangle(14, 14) } def shapeEllipse() : Picture = { Picture.ellipse(20, 9) } def shapeSnake() : Picture = { val part = Picture.ellipse(12, 5) picStack(trans(-8,0) * rot(25) -> part, trans(8,0) * rot(25) -> part) } def innerShape(shape: Picture, c: Color, fill: Int) : Picture = { val innerfill = brit(if(fill==1) 0.8 else 0.0) * sat(if(fill==1) -0.8 else 0.0) * fillColor(if(fill<2) c else white) * penColor(noColor) -> shape val inner = fillColor(noColor) * penColor(c) -> shape picStack(innerfill, inner) } def repeatShape(cnt: Int, shape: Picture) : Picture = { cnt match { case 1 => shape case 2 => picStack(trans(0,-12) -> shape, trans(0,12) -> shape) case 3 => picStack(trans(0,-25) -> shape, shape, trans(0,25) -> shape) } } def shapeNum(i: Int) : Picture = { i match { case 0 => shapeEllipse() case 1 => shapeRaute() case 2 => shapeSnake() } } def colorNum(i: Int) : Color = { i match { case 0 => green case 1 => purple case 2 => red } } def mydraw(pic: Picture) { // in order to avoid double call of draw(pic), we need to call pic.visible() instead if(!pic.isDrawn) { draw(pic) } else { pic.visible() } } case class Card(cnt: Int, fill: Int, shape: Int, color: Int) { val border = trans(-28,-45) * penColor(black) * fillColor(white) -> Picture.rectangle(56, 90) val drawShape = picStack(border, repeatShape(cnt+1, innerShape(shapeNum(shape),colorNum(color),fill))) val drawShape2 = picStack(border, repeatShape(cnt+1, innerShape(shapeNum(shape),colorNum(color),fill))) val mark = penColor(orange) * penWidth(4)-> Picture.ellipse(10, 10) val mark2 = penColor(orange) * penWidth(4) -> Picture.ellipse(10, 10) def drawAt(i: Int, j: Int) { // draw Card on normal board (calling draw() twice needs to be avoided) if(!drawShape.isDrawn) { val translation = trans(-770 + i*65, -80 + j*100) draw(translation -> drawShape) mark.setPosition(drawShape.position) } } def draw4dAt(i: Int, j: Int, k: Int, l: Int) { // draw Card on 4D-hypercube (calling draw() twice needs to be avoided) if(!drawShape2.isDrawn) { val translation = trans(-200 + (i-1)*110 + k*20 + l*370, (j-1) * 140 + k*15) draw(translation -> drawShape2) mark2.setPosition(drawShape2.position) if (mark.isDrawn) { mydraw(mark2) } } } def draw4d() { draw4dAt(cnt, fill, shape, color) } def markCard() { mydraw(mark) if (drawShape2.isDrawn) { mydraw(mark2) } } def unmark() { mark.invisible() mark2.invisible() } def invisible() { // removing a Card is hard, thus we make it only invisible drawShape.invisible() drawShape2.invisible() mark.invisible() mark2.invisible() } def getAttributes() : List[Int] = { List(cnt, fill, shape, color) } def isVisible() : Boolean = { drawShape.isVisible } } def drawGrid() { // draw grid for 4D hypercube (more lines might help but could also distract) val w = 2*110+55+4 val h = 2*140+90+4 for(shape <- 0 to 2) { for(color <- 0 to 2) { draw(opac(-0.5) * penColor(black) * penWidth(1) * trans(-200 - w/2 + shape*20 + color*370, - h/2 + shape*15) -> Picture.rectangle(w, h)) } } } case class PlayerState(id: Int, game: Game, key: String) { var score = 0 val button = Button(s"Player $id: Set!")({game.player_said_set(id)}) var scoreLabel = Picture.textu(s"$score", 18, black) var keyLabel = penColor(black) -> Picture.text(s"$key", 18) val marker = penColor(orange) * penWidth(4) -> Picture.rectangle(120,30) val shape = picStack(Picture.widget(button), trans(60,60) -> scoreLabel, trans(35,0) -> keyLabel) def asShape() : Picture = { shape } def setScore(s: Int) { score = s scoreLabel.update(score) } def incScore(delta: Int) { setScore(score + delta) } def decScore(delta: Int) { setScore(score - delta) } def mark() { // since the shape is positioned outside PlayerState, we copy position marker.setPosition(shape.position) mydraw(marker) } def unmark() { marker.invisible() } } def genCards() = for(i <- 0 to 3*3*3*3-1) yield Card(i%3,(i/3)%3,(i/9)%3,(i/27)%3) class Countdown(label: Picture, timeoutFn: () => Unit) { var active = false var downCounter = 10 var secCounter = 0 def start() { downCounter = 10 label.update(s"$downCounter") secCounter = 0 active = true } def stop() { active = false label.update("") } timer(100) { runInGuiThread { if(active) { secCounter += 1 if(secCounter == 10) { downCounter -= 1 label.update(s"$downCounter") secCounter = 0 if(downCounter == 0) { label.update("timeout!") active = false timeoutFn() } } } } } } case class Game(numPlayers: Int) { val playerKeyLabels = List("Space", "Enter", "Backspace", "L", "", "", "") val playerKeys = Map((Kc.VK_SPACE,1),(Kc.VK_A,1),(Kc.VK_ENTER,2), (Kc.VK_F,2), (Kc.VK_BACK_SPACE,3), (Kc.VK_J,3), (Kc.VK_L,4)) val players = for(i <- 0 until numPlayers) yield PlayerState(i+1,this, playerKeyLabels(i)) val buttonMore = Button(s"More Cards")({onMoreCards()}) val button4d = Button(s"Show 4D")({openCards.foreach{card => if(card.isVisible()) card.draw4d()}}) val buttonNewGame = Button(s"New Game")({onNewGame()}) val countdownLabel = Picture.textu("", 18, black) val countdown = new Countdown(countdownLabel, onTimeout) var activePlayer = -1 val deck = util.Random.shuffle(genCards()).toBuffer var openCards = List[Card]() var markedCards = List[Card]() var blockAction = false buttonNewGame.setFocusable(false) // Enter should never click button button4d.setFocusable(false) // Enter should never click button buttonMore.setFocusable(false) // Enter should never click button onKeyPress { k => if (playerKeys.contains(k)) { val playerId = playerKeys(k) if (playerId <= players.size) { player_said_set(playerId) } } } def onNewGame() { if(!blockAction && activePlayer < 0) { openCards.foreach{card => card.invisible()} markedCards.foreach{card => card.unmark()} openCards = List[Card]() markedCards = List[Card]() players.foreach{player => player.setScore(0)} deck.remove(0, deck.size) deck.appendAll(util.Random.shuffle(genCards())) newCards(12) refresh() } } def onMoreCards() { if(!blockAction && activePlayer < 0 && openCards.filter(card => card.isVisible()).size < 7*3){ newCards(3) refresh() } } def onTimeout() { if(!blockAction && activePlayer >= 0) { failedSet() } } def player_said_set(id: Int) { if (!blockAction && activePlayer < 0) { activePlayer = id-1 players(activePlayer).mark() countdown.start() } } def newCards(n: Int) { // we don't want to move cards already drawn, so we rather replace invisible ones if(openCards.filter(card => card.isVisible()).size + n <= openCards.size) { replaceCards(openCards.filter(card => !card.isVisible()).slice(0,n)) } else { val newN = Math.min(n, deck.size) openCards = openCards ++ deck.slice(0,newN) deck.remove(0,newN) } } def replaceCards(cards: List[Card]) { val newN = Math.min(cards.size, deck.size) val cardMap = Map[Card,Card]() ++ cards.slice(0,newN).zip(deck.slice(0,newN)) // replace cards in-place of list so positions of other cards don't change openCards = openCards.map(card => if(cards.contains(card)) cardMap(card) else card) deck.remove(0,newN) } def onCardClick(card: Card, game: Game) { if(!blockAction){ card.draw4d() if(activePlayer >= 0 && !markedCards.contains(card) && markedCards.size < 3) { card.markCard() markedCards = markedCards ++ List(card) countdown.start() if (markedCards.size >= 3) { countdown.stop() checkSet() } } } } def checkSet() { var foundSet = true for(attribute <- 0 until 4) { var sum=0 markedCards.foreach{ card => sum += card.getAttributes()(attribute) } // adding three numbers (0,1,2) we check for all same or all different // by checking their sum against 0,3,6 if(!List(0,3,6).contains(sum)){ foundSet=false } } if (foundSet) { successSet() } else { failedSet() } } def successSet() { if(activePlayer >= 0) players(activePlayer).incScore(1) blockAction = true runInBackground { Thread.sleep(1000) // allow user to see third click markedCards.foreach{card => card.invisible()} if (openCards.filter(card => card.isVisible()).size < 12 && deck.size > 0) { replaceCards(markedCards) } nextSet() blockAction = false } } def failedSet() { if(activePlayer >= 0) players(activePlayer).decScore(1) blockAction = true runInBackground { Thread.sleep(1000) // allow user to see third click markedCards.foreach{card => card.unmark()} nextSet() blockAction = false } } def nextSet() { markedCards = List[Card]() if(activePlayer >= 0) players(activePlayer).unmark() activePlayer = -1 refresh() } def drawGame() { // this function is called only once to avoid double call of draw() drawGrid() players.zip(0 until players.size).foreach{ case (player, idx) => draw(trans(-800 + idx*150, -200) -> player.asShape()) } draw(trans(-800, 180) -> Picture.widget(buttonMore)) draw(trans(-800 + 120, 180) -> Picture.widget(button4d)) draw(trans(-800 + 220, 180) -> Picture.widget(buttonNewGame)) draw(trans(-800 + 350, 205) -> countdownLabel) refresh() } def refresh() { openCards.zip(0 until openCards.size).foreach{ case (card,i) => { card.drawAt(i/3,i%3); card.drawShape.onMouseClick { (x, y) => onCardClick(card, game) } card.drawShape2.onMouseClick { (x, y) => onCardClick(card, game) } } } } } cleari() toggleFullScreenCanvas() val game = Game(2) game.newCards(12) game.drawGame() activateCanvas()
Your Comment: