Session V

Memory Game

first prototype

15. Dec. 2021, 14:00-17:00

Artful Coding 1: web-based games development

dieAngewandte

the concept

html

the scaffold


              <!DOCTYPE html>
              <html lang="en" dir="ltr">
                <head>
                  
                  
                  memorize!
                </head>
                <body>
                  

memorize!

</body> </html>

the content


              
Health:
Count:

css

not a lot


              body div.container {
                width: 800px;
                margin: auto;
              }

              #cards {
                width: 100%;
                padding: 1em 0;
              }

              #cards .row {
                display: flex;
                justify-content: space-around;
              }

              #cards .card {
                width: 100px;
                height: 100px;
                margin: 1em;
                border: 1px solid black;
                border-radius: 5%;
              }

              #cards .card .content {
                padding-top: 20px;
                text-align: center;
                font-size: 3em;
                display: none;
              }
            

javascript

game config & randInt


              const memoryItemPool = [
                '🌕', '🚀', '🐝', '🍀', '⌛', '📞', '👀', '📪', '📫', '🚃',
                '🚢', '🚇', '⛄', '🍦', '🏂', '🥶', '🐧', '💜', '🔥', '🌶',
                '🍵', '🫖', '⭐', '💋', '🌏', '💪'
              ]

              let currentGameItems = []
              const gameState = {
                status: 'uninitialised',
                processing: false,
                difficulty: 'count only',
                favourite: false,
                stats: {
                  health: 0,
                  count: 0,
                },
                grid: [],
              }

              // little helper function to get a random integer
              function randInt(max) {
                return Math.floor(Math.random() * max);
              }
            

initialisation


              function init () {
                // create new random array of current game items
                let memoryPool = [...memoryItemPool]
                currentGameItems = []
                while (currentGameItems.length < 12) {
                  let i = randInt(memoryPool.length)
                  let [item] = memoryPool.splice(i, 1)
                  currentGameItems.push(item)
                }

                // create a grid with pairs of game items
                let currentItemPool = [...currentGameItems]
                currentItemPool.push(...currentGameItems)
                gameState.grid = []
                for (let row=0; row<4; row++) {
                  gameState.grid.push([])
                  for (let col=0; col<6; col++) {
                    let i = randInt(currentItemPool.length)
                    let [item] = currentItemPool.splice(i, 1)
                    gameState.grid[row].push({
                      item: item,
                      hidden: true,
                      solved: false,
                    })
                  }
                }

                // now create the memory cards from this grid
                createCards(gameState.grid)

                gameState.status = 'initialised'
              }
            

creating cards


              function createCards(grid) {
                $cards = $( '#cards' )
                $cards.children().remove()
                for (let row=0; row<4; row++) {
                  let $row = $( '
' ) $cards.append($row) for (let col=0; col<6; col++) { $card = $( `
${grid[row][col].item}
` ) $card.attr('row', row) $card.attr('col', col) $card.on('click', clickMemoryCard) $row.append($card) } } }

handle card clicks


              function clickMemoryCard(event) {
                // if the current game is completed or the grid is just being processed,
                // ignore all clicks on cards
                if (gameState.status === 'done' || gameState.processing) {
                  return
                }

                // if the card is not yet visible the event.target will be the card itself,
                // but if it is already visible the target will point to the content
                let $card = $( event.target )
                if ($card.hasClass('content')) {
                  $card = $card.parent()
                }

                // fetch row and col attributes and show the card if it is still hidden
                let row = $card.attr('row')
                let col = $card.attr('col')
                if ( gameState.grid[row][col].hidden ) {
                  gameState.grid[row][col].hidden = false
                  // reveal the card slowly and afterwards process the grid
                  $card.children().show( 'slow', processGrid )
                }
              }
            

processing the grid


              function processGrid () {
                gameState.processing = true
                let openUnsolved = getOpenUnsolvedCards()

                if (openUnsolved.length < 2) {
                  gameState.processing = false
                  return
                }

                if (openUnsolved[0].card.item === openUnsolved[1].card.item) {
                  openUnsolved[0].card.solved = true
                  openUnsolved[1].card.solved = true
                  gameState.processing = false
                  return
                }

                // wait for a short amount of time and then hide unsolved cards
                setTimeout(() => {
                  let row, col
                  let $card
                  let $cards = $( '#cards' )
                  row = openUnsolved[0].row
                  col = openUnsolved[0].col
                  gameState.grid[row][col].hidden = true
                  $card = $cards.children().eq(row).children().eq(col)
                  $card.children().hide('slow')
                  row = openUnsolved[1].row
                  col = openUnsolved[1].col
                  gameState.grid[row][col].hidden = true
                  $card = $cards.children().eq(row).children().eq(col)
                  // after the second card is hidden set the processing stat to false
                  $card.children().hide('slow', () => {gameState.processing = false})
                }, 500)
              }
            

a little helper & document ready function


              function getOpenUnsolvedCards () {
                let ret = []
                for (let row=0; row<4; row++) {
                  for (let col=0; col<6; col++) {
                    if (!gameState.grid[row][col].hidden && !gameState.grid[row][col].solved) {
                      ret.push({
                        row: row,
                        col: col,
                        card: gameState.grid[row][col]
                      })
                    }
                  }
                }
                return ret
              }

              $( document ).ready(function () {
                init()
              })
            

A first prototype

memoroji!

Still missing functionality:

  • Implementation of buttons
  • Storage of current state and favourite
  • Difficulty, health and stats

What up next?

You could:

  • Improve the styling, to make the game visually appealing
  • Use images or other items for the memory cards
  • Use effects / animations / sounds on success or errors
  • Implement some of the missing functionality

Minimal effort for this exercise: some styling

If you want to go for substantial extensions and changes, this can already count towards your final project.