From dfe2f51a5b75ece203f4ef6d38a3fbddd7604027 Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Thu, 20 May 2021 14:32:05 +0000
Subject: [PATCH] Resolve "Add pair match questions"

---
 client/src/interfaces/ApiModels.ts            |   6 +-
 .../PresentationEditorPage.tsx                |   2 +-
 .../components/QuestionComponentDisplay.tsx   |  34 +-
 .../components/SlideDisplay.tsx               |  13 +-
 .../components/SlideSettings.tsx              |   5 +
 .../components/TextComponentDisplay.tsx       |   2 +-
 .../components/TextComponentEdit.tsx          |   2 +-
 .../answerComponents/AnswerMatch.tsx          | 134 ++++++++
 .../answerComponents/AnswerMultiple.tsx       |   6 +-
 .../answerComponents/AnswerSingle.tsx         |   2 +-
 .../answerComponents/AnswerText.tsx           |   2 +-
 .../components/answerComponents/styled.tsx    |  40 +++
 .../MatchAlternatives.tsx                     | 316 ++++++++++++++++++
 .../MultipleChoiceAlternatives.tsx            |  25 +-
 .../QuestionSettings.tsx                      |   2 +-
 .../SingleChoiceAlternatives.tsx              |  14 +-
 .../slideSettingsComponents/SlideType.tsx     |  41 ++-
 .../presentationEditor/components/styled.tsx  |   6 +-
 client/src/pages/views/JudgeViewPage.tsx      |  26 +-
 client/src/pages/views/OperatorViewPage.tsx   |   2 +-
 .../views/components/JudgeScoreDisplay.tsx    |  88 ++++-
 .../components/JudgeScoringInstructions.tsx   |   1 -
 client/src/pages/views/components/Timer.tsx   |  28 +-
 client/src/pages/views/components/styled.tsx  |   6 +-
 client/src/utils/renderSlideIcon.tsx          |   3 +
 server/app/apis/alternatives.py               |  32 +-
 server/app/apis/misc.py                       |   6 +-
 server/app/apis/slides.py                     |  36 +-
 server/app/core/schemas.py                    |   6 +-
 server/app/database/controller/add.py         |  25 +-
 server/app/database/controller/get.py         |   9 +-
 server/app/database/controller/utils.py       |  81 +++--
 server/app/database/models.py                 |  21 +-
 server/populate.py                            |   4 +-
 server/tests/test_db.py                       |  15 +-
 35 files changed, 832 insertions(+), 209 deletions(-)
 create mode 100644 client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/MatchAlternatives.tsx

diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts
index 7b5617d8..d32cd65a 100644
--- a/client/src/interfaces/ApiModels.ts
+++ b/client/src/interfaces/ApiModels.ts
@@ -61,8 +61,10 @@ export interface Question extends NameID {
 
 export interface QuestionAlternative {
   id: number
-  text: string
-  value: number
+  alternative: string
+  alternative_order: number
+  correct: string
+  correct_order: number
   question_id: number
 }
 
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index a46262af..9b040b9d 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -131,7 +131,7 @@ const PresentationEditorPage: React.FC = () => {
     setSortedSlides(slidesCopy)
     if (draggedSlideId) {
       await axios
-        .put(`/api/competitions/${competitionId}/slides/${draggedSlideId}/order`, { order: result.destination.index })
+        .put(`/api/competitions/${competitionId}/slides/${draggedSlideId}`, { order: result.destination.index })
         .catch(console.log)
     }
   }
diff --git a/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx b/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx
index 91894c6f..75690ea6 100644
--- a/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx
+++ b/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx
@@ -11,9 +11,10 @@
  * @module
  */
 
-import { Card, Divider, ListItem, Typography } from '@material-ui/core'
+import { AppBar, Card, Divider, Typography } from '@material-ui/core'
 import React from 'react'
 import { useAppSelector } from '../../../hooks'
+import AnswerMatch from './answerComponents/AnswerMatch'
 import AnswerMultiple from './answerComponents/AnswerMultiple'
 import AnswerSingle from './answerComponents/AnswerSingle'
 import AnswerText from './answerComponents/AnswerText'
@@ -80,21 +81,34 @@ const QuestionComponentDisplay = ({ variant, currentSlideId }: QuestionComponent
           )
         }
         return
-
+      case 'Match':
+        if (activeSlide) {
+          return (
+            <AnswerMatch
+              variant={variant}
+              activeSlide={activeSlide}
+              competitionId={activeSlide.competition_id.toString()}
+            />
+          )
+        }
+        return
       default:
         break
     }
   }
 
   return (
-    <Card style={{ maxHeight: '100%', overflowY: 'auto' }}>
-      <ListItem>
-        <Center style={{ justifyContent: 'space-evenly' }}>
-          <Typography>Poäng: {total_score}</Typography>
-          <Typography>{questionName}</Typography>
-          <Typography>Timer: {timer}</Typography>
-        </Center>
-      </ListItem>
+    <Card elevation={4} style={{ maxHeight: '100%', overflowY: 'auto' }}>
+      <AppBar position="relative">
+        <div style={{ display: 'flex', height: 60 }}>
+          <Center style={{ alignItems: 'center' }}>
+            <Typography variant="h5">{questionName}</Typography>
+          </Center>
+        </div>
+        <div style={{ position: 'fixed', right: 5, top: 14, display: 'flex', alignItems: 'center' }}>
+          <Typography variant="h5">{total_score}p</Typography>
+        </div>
+      </AppBar>
       <Divider />
       {getAlternatives()}
     </Card>
diff --git a/client/src/pages/presentationEditor/components/SlideDisplay.tsx b/client/src/pages/presentationEditor/components/SlideDisplay.tsx
index 897a321c..b6505eed 100644
--- a/client/src/pages/presentationEditor/components/SlideDisplay.tsx
+++ b/client/src/pages/presentationEditor/components/SlideDisplay.tsx
@@ -1,4 +1,5 @@
-import { Typography } from '@material-ui/core'
+import { Card, Typography } from '@material-ui/core'
+import TimerIcon from '@material-ui/icons/Timer'
 import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
 import { getTypes } from '../../../actions/typesAction'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
@@ -60,11 +61,15 @@ const SlideDisplay = ({ variant, activeViewTypeId, currentSlideId }: SlideDispla
       <SlideEditorContainerRatio>
         <SlideEditorPaper ref={editorPaperRef}>
           <SlideDisplayText $scale={scale}>
-            {variant === 'editor' && slide?.timer ? `Tid kvar: ${slide?.timer}` : ''}
-            {variant === 'presentation' && <Timer />}
+            {slide?.timer && (
+              <Card style={{ display: 'flex', alignItems: 'center', padding: 10 }}>
+                <TimerIcon fontSize="large" />
+                <Timer variant={variant} />
+              </Card>
+            )}
           </SlideDisplayText>
           <SlideDisplayText $scale={scale} $right>
-            {slide && `Sida: ${slide?.order + 1} / ${totalSlides}`}
+            <Card style={{ padding: 10 }}>{slide && `${slide?.order + 1} / ${totalSlides}`}</Card>
           </SlideDisplayText>
           {(competitionBackgroundImage || slideBackgroundImage) && (
             <img
diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index 6b4a8d5e..68abd240 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -7,6 +7,7 @@ import { useAppSelector } from '../../../hooks'
 import BackgroundImageSelect from './BackgroundImageSelect'
 import Images from './slideSettingsComponents/Images'
 import Instructions from './slideSettingsComponents/Instructions'
+import MatchAlternatives from './slideSettingsComponents/MatchAlternatives'
 import MultipleChoiceAlternatives from './slideSettingsComponents/MultipleChoiceAlternatives'
 import QuestionSettings from './slideSettingsComponents/QuestionSettings'
 import SingleChoiceAlternatives from './slideSettingsComponents/SingleChoiceAlternatives'
@@ -54,6 +55,10 @@ const SlideSettings: React.FC = () => {
         <SingleChoiceAlternatives activeSlide={activeSlide} competitionId={competitionId} />
       )}
 
+      {activeSlide?.questions[0]?.type_id === 5 && (
+        <MatchAlternatives activeSlide={activeSlide} competitionId={competitionId} />
+      )}
+
       {activeSlide && (
         <Texts activeViewTypeId={activeViewTypeId} activeSlide={activeSlide} competitionId={competitionId} />
       )}
diff --git a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx
index b2e11200..5cc87ec9 100644
--- a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx
+++ b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { ImageComponent, TextComponent } from '../../../interfaces/ApiModels'
+import { TextComponent } from '../../../interfaces/ApiModels'
 
 type TextComponentDisplayProps = {
   component: TextComponent
diff --git a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx
index 61dc1284..a42bdb03 100644
--- a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx
+++ b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx
@@ -37,7 +37,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => {
     setTimerHandle(
       window.setTimeout(async () => {
         await axios.put(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${component.id}`, {
-          text: newText,
+          alternative: newText,
         })
         dispatch(getEditorCompetition(competitionId))
       }, 250)
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx
new file mode 100644
index 00000000..d2d6c3e2
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx
@@ -0,0 +1,134 @@
+/**
+ * What it is:
+ * Contains the component for the multiple choice question type ("Kryssfråga")
+ * which is displayed in the participant view in the editor and presentation.
+ * This is a part of a question component which the users will interact with to answer multiple choice questions.
+ * The participants get multiple alternatives and can mark multiple of these alternatives as correct.
+ *
+ * How it's used:
+ * This file is used when a question component is to be rendered which only happens in QuestionComponentDisplay.tsx.
+ * For more information read the documentation of that file.
+ *
+ * @module
+ */
+
+import { ListItemText, Typography } from '@material-ui/core'
+import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'
+import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'
+import SyncAltIcon from '@material-ui/icons/SyncAlt'
+import axios from 'axios'
+import React, { useEffect, useState } from 'react'
+import { getPresentationCompetition } from '../../../../actions/presentation'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { QuestionAlternative } from '../../../../interfaces/ApiModels'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { Center, Clickable } from '../styled'
+import { MatchButtonContainer, MatchCard, MatchContainer, MatchCorrectContainer, MatchIconContainer } from './styled'
+
+type AnswerMultipleProps = {
+  variant: 'editor' | 'presentation'
+  activeSlide: RichSlide | undefined
+  competitionId: string
+}
+
+const AnswerMatch = ({ variant, activeSlide, competitionId }: AnswerMultipleProps) => {
+  const dispatch = useAppDispatch()
+  const teamId = useAppSelector((state) => state.competitionLogin.data?.team_id)
+  const [sortedAlternatives, setSortedAlternatives] = useState<QuestionAlternative[]>([])
+  const [sortedAnswers, setSortedAnswers] = useState<QuestionAlternative[]>([])
+  const team = useAppSelector((state) => state.presentation.competition.teams.find((team) => team.id === teamId))
+
+  useEffect(() => {
+    if (activeSlide) {
+      setSortedAlternatives([
+        ...activeSlide.questions[0].alternatives.sort((a, b) => (a.alternative_order > b.alternative_order ? 1 : -1)),
+      ])
+      setSortedAnswers([
+        ...activeSlide.questions[0].alternatives.sort((a, b) => (a.correct_order > b.correct_order ? 1 : -1)),
+      ])
+    }
+  }, [activeSlide])
+
+  useEffect(() => {
+    // Send the standard answers ( if the team choses to not move one of the answers )
+    if (teamId && team?.question_alternative_answers.length === 0) {
+      activeSlide?.questions[0].alternatives.forEach((alternative) => {
+        const answer = activeSlide?.questions[0].alternatives.find(
+          (alt) => alternative.alternative_order === alt.correct_order
+        )
+        axios
+          .put(`/api/competitions/${competitionId}/teams/${teamId}/answers/question_alternatives/${alternative.id}`, {
+            answer: `${alternative.alternative} - ${answer?.correct}`,
+          })
+          .then(() => {
+            dispatch(getPresentationCompetition(competitionId))
+          })
+          .catch(console.log)
+      })
+    }
+  }, [teamId])
+
+  const onMove = async (previousIndex: number, resultIndex: number) => {
+    // moved outside the list
+    if (resultIndex < 0 || resultIndex >= sortedAnswers.length || variant !== 'presentation') return
+    const answersCopy = [...sortedAnswers]
+    const [removed] = answersCopy.splice(previousIndex, 1)
+    answersCopy.splice(resultIndex, 0, removed)
+    setSortedAnswers(answersCopy)
+
+    sortedAlternatives.forEach((alternative, index) => {
+      const answeredText = answersCopy[index].correct
+      if (!activeSlide) return
+      axios
+        .put(`/api/competitions/${competitionId}/teams/${teamId}/answers/question_alternatives/${alternative.id}`, {
+          answer: `${alternative.alternative} - ${answeredText}`,
+        })
+        .catch(console.log)
+    })
+  }
+
+  return (
+    <>
+      <Center>
+        <ListItemText secondary="Para ihop de alternativ som hör ihop:" />
+      </Center>
+      <MatchContainer>
+        <div style={{ flexDirection: 'column', marginRight: 20 }}>
+          {sortedAlternatives.map((alternative, index) => (
+            <MatchCard key={alternative.id} elevation={4}>
+              <Typography id="outlined-basic">{alternative.alternative}</Typography>
+            </MatchCard>
+          ))}
+        </div>
+
+        <div style={{ flexDirection: 'column', marginRight: 20 }}>
+          {sortedAlternatives.map((alternative, index) => (
+            <MatchIconContainer key={alternative.id}>
+              <SyncAltIcon />
+            </MatchIconContainer>
+          ))}
+        </div>
+
+        <div style={{ flexDirection: 'column' }}>
+          {sortedAnswers.map((alternative, index) => (
+            <MatchCard key={alternative.id} elevation={4}>
+              <MatchCorrectContainer>
+                <Typography id="outlined-basic">{alternative.correct}</Typography>
+              </MatchCorrectContainer>
+              <MatchButtonContainer>
+                <Clickable>
+                  <KeyboardArrowUpIcon onClick={() => onMove(index, index - 1)} />
+                </Clickable>
+                <Clickable>
+                  <KeyboardArrowDownIcon onClick={() => onMove(index, index + 1)} />
+                </Clickable>
+              </MatchButtonContainer>
+            </MatchCard>
+          ))}
+        </div>
+      </MatchContainer>
+    </>
+  )
+}
+
+export default AnswerMatch
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
index 0dd7efcf..5868ef11 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
@@ -50,10 +50,6 @@ const AnswerMultiple = ({ variant, activeSlide, competitionId }: AnswerMultipleP
     if (!activeSlide) {
       return
     }
-
-    //const checkedValue = checked ? 1 : 0
-    //const correctAnswer = checkedValue === alternative.value ? 1 : 0
-
     const url = `/api/competitions/${competitionId}/teams/${teamId}/answers/question_alternatives/${alternative.id}`
     const payload = {
       answer: checked ? 1 : 0,
@@ -97,7 +93,7 @@ const AnswerMultiple = ({ variant, activeSlide, competitionId }: AnswerMultipleP
                 checked={decideChecked(alt)}
                 onChange={(event: any) => updateAnswer(alt, event.target.checked)}
               />
-              <Typography style={{ wordBreak: 'break-all' }}>{alt.text}</Typography>
+              <Typography style={{ wordBreak: 'break-all' }}>{alt.alternative}</Typography>
             </ListItem>
           </div>
         ))}
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
index 514200d1..1bd16da5 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
@@ -110,7 +110,7 @@ const AnswerSingle = ({ variant, activeSlide, competitionId }: AnswerSingleProps
           <div key={alt.id}>
             <ListItem divider>
               {renderRadioButton(alt)}
-              <Typography style={{ wordBreak: 'break-all' }}>{alt.text}</Typography>
+              <Typography style={{ wordBreak: 'break-all' }}>{alt.alternative}</Typography>
             </ListItem>
           </div>
         ))}
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
index e96f65bc..f3dccfd5 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
@@ -48,7 +48,7 @@ const AnswerText = ({ activeSlide, competitionId }: AnswerTextProps) => {
     const alternative = activeSlide.questions[0].alternatives[0]
     const url = `/api/competitions/${competitionId}/teams/${teamId}/answers/question_alternatives/${alternative.id}`
     await axios
-      .put(url, { answer: answer })
+      .put(url, { answer })
       .then(() => {
         dispatch(getPresentationCompetition(competitionId))
       })
diff --git a/client/src/pages/presentationEditor/components/answerComponents/styled.tsx b/client/src/pages/presentationEditor/components/answerComponents/styled.tsx
index 9140e3c2..109776fe 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/styled.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/styled.tsx
@@ -1,5 +1,45 @@
+import { Card } from '@material-ui/core'
 import styled from 'styled-components'
 
 export const AnswerTextFieldContainer = styled.div`
   height: calc(100% - 90px);
 `
+export const MatchContainer = styled.div`
+  margin-bottom: 50px;
+  margin-top: 10px;
+  display: flex;
+  justify-content: center;
+`
+
+export const MatchCard = styled(Card)`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 80px;
+  min-width: 150px;
+  margin-bottom: 5px;
+`
+
+export const MatchIconContainer = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 80px;
+  margin-bottom: 5px;
+`
+
+export const MatchButtonContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  margin-right: 5px;
+`
+
+export const MatchCorrectContainer = styled.div`
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/MatchAlternatives.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/MatchAlternatives.tsx
new file mode 100644
index 00000000..0a944000
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/MatchAlternatives.tsx
@@ -0,0 +1,316 @@
+/**
+ * Lets a competition creator add, remove and handle alternatives for single choice questions ("Alternativfråga") in the slide settings panel.
+ *
+ * @module
+ */
+
+import {
+  AppBar,
+  Button,
+  Card,
+  Dialog,
+  DialogActions,
+  DialogContent,
+  DialogContentText,
+  DialogTitle,
+  IconButton,
+  ListItem,
+  ListItemText,
+  Tab,
+  Tabs,
+  Typography,
+} from '@material-ui/core'
+import ClearIcon from '@material-ui/icons/Clear'
+import DragIndicatorIcon from '@material-ui/icons/DragIndicator'
+import axios from 'axios'
+import React, { useEffect } from 'react'
+import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'
+import { getEditorCompetition } from '../../../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { QuestionAlternative } from '../../../../interfaces/ApiModels'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { AddButton, AlternativeTextField, Center, SettingsList } from '../styled'
+
+type SingleChoiceAlternativeProps = {
+  activeSlide: RichSlide
+  competitionId: string
+}
+
+interface AlternativeUpdate {
+  alternative?: string
+  alternative_order?: string
+  correct?: string
+  correct_order?: string
+}
+
+const MatchAlternatives = ({ activeSlide, competitionId }: SingleChoiceAlternativeProps) => {
+  const dispatch = useAppDispatch()
+  const [dialogOpen, setDialogOpen] = React.useState(false)
+  const [selectedTab, setSelectedTab] = React.useState(0)
+  const activeSlideId = useAppSelector((state) => state.editor.activeSlideId)
+  const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined)
+  // Locally stored sorted versions of alternatives to make the sorting smoother, and not have to wait on backend
+  const [alternativesSortedByAlternative, setAlternativesSortedByAlternative] = React.useState<QuestionAlternative[]>(
+    []
+  )
+  const [alternativesSortedByCorrect, setAlternativesSortedByCorrect] = React.useState<QuestionAlternative[]>([])
+  useEffect(() => {
+    if (!activeSlide?.questions[0].alternatives) return
+    setAlternativesSortedByAlternative([
+      ...activeSlide?.questions[0].alternatives.sort((a, b) => (a.alternative_order > b.alternative_order ? 1 : -1)),
+    ])
+    setAlternativesSortedByCorrect([
+      ...activeSlide?.questions[0].alternatives.sort((a, b) => (a.correct_order > b.correct_order ? 1 : -1)),
+    ])
+  }, [activeSlide])
+
+  const onDragEnd = async (result: DropResult, orderType: 'alternative_order' | 'correct_order') => {
+    // dropped outside the list or same place
+    if (!result.destination || result.destination.index === result.source.index) return
+
+    const draggedIndex = result.source.index
+    const draggedAlternativeId = activeSlide?.questions[0].alternatives.find((alt) => alt[orderType] === draggedIndex)
+      ?.id
+    if (orderType === 'alternative_order') {
+      const alternativesCopy = [...alternativesSortedByAlternative]
+      const [removed] = alternativesCopy.splice(draggedIndex, 1)
+      alternativesCopy.splice(result.destination.index, 0, removed)
+      setAlternativesSortedByAlternative(alternativesCopy)
+    } else {
+      const alternativesCopy = [...alternativesSortedByCorrect]
+      const [removed] = alternativesCopy.splice(draggedIndex, 1)
+      alternativesCopy.splice(result.destination.index, 0, removed)
+      setAlternativesSortedByCorrect(alternativesCopy)
+    }
+    if (!draggedAlternativeId) return
+    await axios
+      .put(
+        `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${draggedAlternativeId}`,
+        {
+          [orderType]: result.destination.index,
+        }
+      )
+      .then(() => {
+        dispatch(getEditorCompetition(competitionId))
+      })
+      .catch(console.log)
+  }
+
+  const updateAlternative = async (alternative_id: number, alternativeUpdate: AlternativeUpdate) => {
+    if (timerHandle) {
+      clearTimeout(timerHandle)
+      setTimerHandle(undefined)
+    }
+    //Only updates filter and api 250ms after last input was made
+    setTimerHandle(
+      window.setTimeout(() => {
+        if (activeSlide && activeSlide.questions[0]) {
+          axios
+            .put(
+              `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`,
+              alternativeUpdate
+            )
+            .then(() => {
+              dispatch(getEditorCompetition(competitionId))
+            })
+            .catch(console.log)
+        }
+      }, 250)
+    )
+  }
+
+  const addAlternative = async () => {
+    if (activeSlide && activeSlide.questions[0]) {
+      await axios
+        .post(
+          `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`
+        )
+        .then(() => {
+          dispatch(getEditorCompetition(competitionId))
+        })
+        .catch(console.log)
+    }
+  }
+
+  const deleteAlternative = async (alternative_id: number) => {
+    if (activeSlide?.questions[0]) {
+      await axios
+        .delete(
+          `/api/competitions/${competitionId}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`
+        )
+        .then(() => {
+          dispatch(getEditorCompetition(competitionId))
+        })
+        .catch(console.log)
+    }
+  }
+
+  return (
+    <SettingsList>
+      <ListItem divider>
+        <Center>
+          <ListItemText primary="Svarsalternativ" />
+        </Center>
+      </ListItem>
+      {activeSlide?.questions?.[0]?.alternatives?.map((alt) => (
+        <div key={alt.id}>
+          <ListItem divider>
+            <Typography>
+              {alt.alternative} | {alt.correct}
+            </Typography>
+          </ListItem>
+        </div>
+      ))}
+      <Dialog
+        fullWidth
+        open={dialogOpen}
+        onClose={() => console.log('close')}
+        aria-labelledby="responsive-dialog-title"
+      >
+        <DialogTitle id="responsive-dialog-title">Redigera para ihop-alternativ</DialogTitle>
+        <DialogContent style={{ height: '60vh', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+          <AppBar position="relative">
+            <Tabs value={selectedTab} onChange={(event, selectedTab) => setSelectedTab(selectedTab)} centered>
+              <Tab label="Lag" />
+              <Tab label="Facit" color="primary" />
+            </Tabs>
+          </AppBar>
+
+          {selectedTab === 0 && (
+            <div style={{ marginBottom: 50, marginTop: 10 }}>
+              <div style={{ display: 'flex', justifyContent: 'center' }}>
+                {activeSlide?.questions[0].alternatives.length !== 0 && (
+                  <DialogContentText>Para ihop alternativen som de kommer se ut för lagen.</DialogContentText>
+                )}
+                {activeSlide?.questions[0].alternatives.length === 0 && (
+                  <DialogContentText>
+                    Det finns inga alternativ, lägg till alternativ med knappen nedan.
+                  </DialogContentText>
+                )}
+              </div>
+              <div style={{ display: 'flex' }}>
+                <DragDropContext onDragEnd={(result) => onDragEnd(result, 'alternative_order')}>
+                  <div style={{ flexDirection: 'column' }}>
+                    <Droppable droppableId="droppable1">
+                      {(provided) => (
+                        <div ref={provided.innerRef} {...provided.droppableProps}>
+                          {alternativesSortedByAlternative.map((alternative, index) => (
+                            <Draggable draggableId={alternative.id.toString()} index={index} key={alternative.id}>
+                              {(provided, snapshot) => (
+                                <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
+                                  <Card elevation={4} style={{ display: 'flex', alignItems: 'center' }}>
+                                    <DragIndicatorIcon elevation={3} />
+                                    <AlternativeTextField
+                                      id="outlined-basic"
+                                      defaultValue={alternative.alternative}
+                                      onChange={(event) =>
+                                        updateAlternative(alternative.id, { alternative: event.target.value })
+                                      }
+                                      variant="outlined"
+                                      style={{ width: 200 }}
+                                    />
+                                  </Card>
+                                </div>
+                              )}
+                            </Draggable>
+                          ))}
+                          {provided.placeholder}
+                        </div>
+                      )}
+                    </Droppable>
+                  </div>
+                </DragDropContext>
+
+                <DragDropContext onDragEnd={(result) => onDragEnd(result, 'correct_order')}>
+                  <div style={{ flexDirection: 'column' }}>
+                    <Droppable droppableId="droppable2">
+                      {(provided) => (
+                        <div ref={provided.innerRef} {...provided.droppableProps}>
+                          {alternativesSortedByCorrect.map((alternative, index) => (
+                            <Draggable draggableId={alternative.id.toString()} index={index} key={alternative.id}>
+                              {(provided, snapshot) => (
+                                <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
+                                  <Card elevation={4} style={{ display: 'flex', alignItems: 'center' }}>
+                                    <AlternativeTextField
+                                      id="outlined-basic"
+                                      defaultValue={alternative.correct}
+                                      onChange={(event) =>
+                                        updateAlternative(alternative.id, { correct: event.target.value })
+                                      }
+                                      variant="outlined"
+                                    />
+                                    <DragIndicatorIcon elevation={3} />
+                                  </Card>
+                                </div>
+                              )}
+                            </Draggable>
+                          ))}
+                          {provided.placeholder}
+                        </div>
+                      )}
+                    </Droppable>
+                  </div>
+                </DragDropContext>
+              </div>
+            </div>
+          )}
+
+          {selectedTab === 1 && (
+            <div style={{ marginBottom: 50, marginTop: 10 }}>
+              <div style={{ display: 'flex', justifyContent: 'center' }}>
+                {activeSlide?.questions[0].alternatives.length !== 0 && (
+                  <DialogContentText>Editera svarsalternativen.</DialogContentText>
+                )}
+                {activeSlide?.questions[0].alternatives.length === 0 && (
+                  <DialogContentText>
+                    Det finns inga alternativ, lägg till alternativ med knappen nedan.
+                  </DialogContentText>
+                )}
+              </div>
+              {activeSlide?.questions?.[0]?.alternatives?.map((alternative, index) => (
+                <div style={{ display: 'flex' }} key={alternative.id}>
+                  <Card elevation={4} style={{ display: 'flex', alignItems: 'center' }}>
+                    <IconButton size="small" onClick={() => deleteAlternative(alternative.id)}>
+                      <ClearIcon color="error" />
+                    </IconButton>
+                    <AlternativeTextField
+                      id="outlined-basic"
+                      defaultValue={alternative.alternative}
+                      onChange={(event) => updateAlternative(alternative.id, { alternative: event.target.value })}
+                      variant="outlined"
+                      style={{ width: 200 }}
+                    />
+                  </Card>
+                  <Card elevation={4} style={{ display: 'flex', alignItems: 'center' }}>
+                    <AlternativeTextField
+                      id="outlined-basic"
+                      defaultValue={alternative.correct}
+                      onChange={(event) => updateAlternative(alternative.id, { correct: event.target.value })}
+                      variant="outlined"
+                      style={{ width: 200 }}
+                    />
+                  </Card>
+                </div>
+              ))}
+            </div>
+          )}
+        </DialogContent>
+        <DialogActions>
+          <Button variant="contained" autoFocus onClick={addAlternative} color="primary">
+            Lägg till alternativ
+          </Button>
+          <Button variant="contained" autoFocus onClick={() => setDialogOpen(false)} color="secondary">
+            Stäng
+          </Button>
+        </DialogActions>
+      </Dialog>
+      <ListItem button onClick={() => setDialogOpen(true)}>
+        <Center>
+          <AddButton variant="button">Redigera alternativ</AddButton>
+        </Center>
+      </ListItem>
+    </SettingsList>
+  )
+}
+
+export default MatchAlternatives
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx
index 60d741c8..17f09647 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx
@@ -38,21 +38,20 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
   /**
    * A checked checkbox is represented with 1 and an unchecked with 0.
    */
-  const numberToBool = (num: number) => {
-    if (num === 0) return false
+  const stringToBool = (num: string) => {
+    if (num === '0') return false
     else return true
   }
 
   const updateAlternativeValue = async (alternative: QuestionAlternative) => {
     if (activeSlide && activeSlide.questions?.[0]) {
-      let newValue: number
-      if (alternative.value === 0) {
-        newValue = 1
-      } else newValue = 0
-      await axios
-        .put(
+      let newValue: string
+      if (alternative.correct === '0') {
+        newValue = '1'
+      } else newValue = '0'
+      await axios        .put(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`,
-          { value: newValue }
+          { correct: newValue }
         )
         .then(() => {
           dispatch(getEditorCompetition(competitionId))
@@ -66,7 +65,7 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
       await axios
         .put(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`,
-          { text: newText }
+          { alternative: newText }
         )
         .then(() => {
           dispatch(getEditorCompetition(competitionId))
@@ -80,7 +79,7 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
       await axios
         .post(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`,
-          { text: '', value: 0 }
+          { correct: '0' }
         )
         .then(() => {
           dispatch(getEditorCompetition(competitionId))
@@ -117,11 +116,11 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
           <ListItem divider>
             <AlternativeTextField
               id="outlined-basic"
-              defaultValue={alt.text}
+              defaultValue={alt.alternative}
               onChange={(event) => updateAlternativeText(alt.id, event.target.value)}
               variant="outlined"
             />
-            <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} />
+            <GreenCheckbox checked={stringToBool(alt.correct)} onChange={() => updateAlternativeValue(alt)} />
             <Clickable>
               <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} />
             </Clickable>
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx
index 6fe2d510..c2ea6e8e 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx
@@ -97,7 +97,7 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps)
         <Center>
           <SettingsItemContainer>
             <TextField
-              fullWidth={true}
+              fullWidth
               variant="outlined"
               placeholder="Antal poäng"
               helperText="Välj hur många poäng frågan ska ge för rätt svar.   Lämna blank för att inte använda poängfunktionen"
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SingleChoiceAlternatives.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SingleChoiceAlternatives.tsx
index 16604f81..51de69da 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SingleChoiceAlternatives.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SingleChoiceAlternatives.tsx
@@ -28,19 +28,19 @@ const SingleChoiceAlternatives = ({ activeSlide, competitionId }: SingleChoiceAl
   const updateAlternativeValue = async (alternative: QuestionAlternative) => {
     if (activeSlide && activeSlide.questions[0]) {
       // Remove check from previously checked alternative
-      const previousCheckedAltId = activeSlide.questions[0].alternatives.find((alt) => alt.value === 1)?.id
+      const previousCheckedAltId = activeSlide.questions[0].alternatives.find((alt) => alt.correct === '1')?.id
       if (previousCheckedAltId !== alternative.id) {
         if (previousCheckedAltId) {
           axios.put(
             `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${previousCheckedAltId}`,
-            { value: 0 }
+            { correct: '0' }
           )
         }
         // Set new checked alternative
         await axios
           .put(
             `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`,
-            { value: 1 }
+            { correct: '1' }
           )
           .then(() => {
             dispatch(getEditorCompetition(competitionId))
@@ -55,7 +55,7 @@ const SingleChoiceAlternatives = ({ activeSlide, competitionId }: SingleChoiceAl
       await axios
         .put(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`,
-          { text: newText }
+          { alternative: newText }
         )
         .then(() => {
           dispatch(getEditorCompetition(competitionId))
@@ -69,7 +69,7 @@ const SingleChoiceAlternatives = ({ activeSlide, competitionId }: SingleChoiceAl
       await axios
         .post(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`,
-          { text: '', value: 0 }
+          { correct: '0' }
         )
         .then(() => {
           dispatch(getEditorCompetition(competitionId))
@@ -92,7 +92,7 @@ const SingleChoiceAlternatives = ({ activeSlide, competitionId }: SingleChoiceAl
   }
 
   const renderRadioButton = (alt: QuestionAlternative) => {
-    if (alt.value) return <RadioButtonCheckedIcon onClick={() => updateAlternativeValue(alt)} />
+    if (alt.correct === '1') return <RadioButtonCheckedIcon onClick={() => updateAlternativeValue(alt)} />
     else return <RadioButtonUncheckedIcon onClick={() => updateAlternativeValue(alt)} />
   }
 
@@ -114,7 +114,7 @@ const SingleChoiceAlternatives = ({ activeSlide, competitionId }: SingleChoiceAl
             <ListItem divider>
               <AlternativeTextField
                 id="outlined-basic"
-                defaultValue={alt.text}
+                defaultValue={alt.alternative}
                 onChange={(event) => updateAlternativeText(alt.id, event.target.value)}
                 variant="outlined"
               />
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
index e8faa58e..fdecde8e 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
@@ -78,18 +78,17 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
             })
             .then(({ data }) => {
               dispatch(getEditorCompetition(competitionId))
-              removeQuestionComponent().then(() => createQuestionComponent(data.id))
+              removeQuestionComponent().then(() => {
+                //No question component for practical questions
+                if (selectedSlideType !== 2) createQuestionComponent(data.id)
+              })
             })
             .catch(console.log)
           if (selectedSlideType === 1) {
             // Add an alternative to text questions to allow giving answers.
             await axios
               .post(
-                `/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}/alternatives`,
-                {
-                  text: '',
-                  value: 1,
-                }
+                `/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}/alternatives`
               )
               .then(({ data }) => {
                 dispatch(getEditorCompetition(competitionId))
@@ -107,24 +106,19 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
           })
           .then(({ data }) => {
             dispatch(getEditorCompetition(competitionId))
-            createQuestionComponent(data.id)
+            //No question component for practical questions
+            if (selectedSlideType !== 2) createQuestionComponent(data.id)
+            if (selectedSlideType === 1) {
+              // Add an alternative to text questions to allow giving answers.
+              axios
+                .post(`/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${data.id}/alternatives`)
+                .then(({ data }) => {
+                  dispatch(getEditorCompetition(competitionId))
+                })
+                .catch(console.log)
+            }
           })
           .catch(console.log)
-        if (selectedSlideType === 1) {
-          // Add an alternative to text questions to allow giving answers.
-          await axios
-            .post(
-              `/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}/alternatives`,
-              {
-                text: '',
-                value: 1,
-              }
-            )
-            .then(({ data }) => {
-              dispatch(getEditorCompetition(competitionId))
-            })
-            .catch(console.log)
-        }
       }
     }
   }
@@ -176,6 +170,9 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
             <MenuItem value={4} button onClick={() => openSlideTypeDialog(4)}>
               <Typography>Alternativfråga</Typography>
             </MenuItem>
+            <MenuItem value={5} button onClick={() => openSlideTypeDialog(5)}>
+              <Typography>Para ihop-fråga</Typography>
+            </MenuItem>
           </Select>
         </FormControl>
 
diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx
index 1a4f9169..106e360e 100644
--- a/client/src/pages/presentationEditor/components/styled.tsx
+++ b/client/src/pages/presentationEditor/components/styled.tsx
@@ -153,8 +153,8 @@ interface SlideDisplayTextProps {
 
 export const SlideDisplayText = styled(Typography)<SlideDisplayTextProps>`
   position: absolute;
-  top: 5px;
-  left: ${(props) => (props.$right ? undefined : 5)}px;
-  right: ${(props) => (props.$right ? 5 : undefined)}px;
+  top: 0px;
+  left: ${(props) => (props.$right ? undefined : 0)}px;
+  right: ${(props) => (props.$right ? 0 : undefined)}px;
   font-size: ${(props) => 24 * props.$scale}px;
 `
diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx
index a2874024..df80d4fc 100644
--- a/client/src/pages/views/JudgeViewPage.tsx
+++ b/client/src/pages/views/JudgeViewPage.tsx
@@ -124,17 +124,19 @@ const JudgeViewPage: React.FC = () => {
         <div className={classes.toolbar} />
         <List>
           {slides.map((slide, index) => (
-            <SlideListItem
-              selected={slide.order === currentSlide?.order}
-              onClick={() => handleSelectSlide(index)}
-              divider
-              button
-              key={slide.id}
-              style={{ border: 2, borderStyle: slide.id === operatorActiveSlideId ? 'dashed' : 'none' }}
-            >
-              {renderSlideIcon(slide)}
-              <ListItemText primary={`Sida ${slide.order + 1}`} />
-            </SlideListItem>
+            <>
+              <SlideListItem
+                selected={slide.order === currentSlide?.order}
+                onClick={() => handleSelectSlide(index)}
+                button
+                key={slide.id}
+                style={{ border: 2, borderStyle: slide.id === operatorActiveSlideId ? 'dashed' : 'none' }}
+              >
+                {renderSlideIcon(slide)}
+                <ListItemText primary={`Sida ${slide.order + 1}`} />
+              </SlideListItem>
+              <Divider />
+            </>
           ))}
         </List>
       </LeftDrawer>
@@ -153,7 +155,7 @@ const JudgeViewPage: React.FC = () => {
           </ScoreHeaderPaper>
         )}
         <ScoreHeaderPadding />
-        <List style={{ overflowY: 'scroll', overflowX: 'hidden' }}>
+        <List style={{ overflowY: 'auto', overflowX: 'hidden' }}>
           {teams &&
             teams.map((answer, index) => (
               <div key={answer.name}>
diff --git a/client/src/pages/views/OperatorViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx
index 9dab453b..130240c2 100644
--- a/client/src/pages/views/OperatorViewPage.tsx
+++ b/client/src/pages/views/OperatorViewPage.tsx
@@ -317,7 +317,7 @@ const OperatorViewPage: React.FC = () => {
                 color="primary"
               >
                 <TimerIcon fontSize="large" />
-                <Timer disableText />
+                <Timer variant="presentation" />
               </OperatorButton>
             </div>
           </Tooltip>
diff --git a/client/src/pages/views/components/JudgeScoreDisplay.tsx b/client/src/pages/views/components/JudgeScoreDisplay.tsx
index e213d844..40f545ec 100644
--- a/client/src/pages/views/components/JudgeScoreDisplay.tsx
+++ b/client/src/pages/views/components/JudgeScoreDisplay.tsx
@@ -1,4 +1,4 @@
-import { Box, Divider, Typography } from '@material-ui/core'
+import { Box, Card, Divider, Typography } from '@material-ui/core'
 import axios from 'axios'
 import React from 'react'
 import { getPresentationCompetition } from '../../../actions/presentation'
@@ -11,6 +11,7 @@ import {
   ScoreDisplayContainer,
   ScoreDisplayHeader,
   ScoreInput,
+  UnderlinedTypography,
 } from './styled'
 
 type ScoreDisplayProps = {
@@ -25,9 +26,16 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
 
   const activeQuestion = activeSlide.questions[0]
   const activeScore = currentTeam.question_scores.find((x) => x.question_id === activeQuestion?.id)
-  const questionMaxScore = activeQuestion?.total_score
 
-  const scores = currentTeam.question_scores.map((questionAnswer) => questionAnswer.score)
+  const questions = useAppSelector((state) => state.presentation.competition.slides.map((slide) => slide.questions[0]))
+  const teamScores = [...currentTeam.question_scores.map((score) => score)]
+  const scores: (number | undefined)[] = []
+  for (const question of questions) {
+    const correctTeamScore = teamScores.find((score) => question && score.question_id === question.id)
+    if (correctTeamScore !== undefined) {
+      scores.push(correctTeamScore.score)
+    } else scores.push(undefined)
+  }
   const handleEditScore = async (newScore: number, questionId: number) => {
     await axios
       .put(`/api/competitions/${currentCompetititonId}/teams/${currentTeam.id}/answers/question_scores/${questionId}`, {
@@ -36,6 +44,14 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
       .then(() => dispatch(getPresentationCompetition(currentCompetititonId.toString())))
   }
 
+  const sumTwoScores = (a: number | undefined, b: number | undefined) => {
+    let aValue = 0
+    let bValue = 0
+    aValue = a ? a : 0
+    bValue = b ? b : 0
+    return aValue + bValue
+  }
+
   const getAnswers = () => {
     const result: string[] = []
     if (!activeQuestion) {
@@ -46,11 +62,11 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
       if (!ans) {
         continue
       }
-      if (activeQuestion.type_id === 1) {
-        // Text question
+      if (activeQuestion.type_id === 1 || activeQuestion.type_id === 5) {
+        // Text question or match question
         result.push(ans.answer)
       } else if (+ans.answer > 0) {
-        result.push(alt.text)
+        result.push(alt.alternative)
       }
     }
     return result
@@ -62,9 +78,12 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
       return result
     }
     for (const alt of activeQuestion.alternatives) {
-      if (activeQuestion.type_id !== 1 && +alt.value > 0) {
+      // Match question
+      if (activeQuestion.type_id === 5) {
+        result.push(`${alt.alternative} - ${alt.correct}`)
+      } else if (activeQuestion.type_id !== 1 && +alt.correct > 0) {
         // Not text question and correct answer
-        result.push(alt.text)
+        result.push(alt.alternative)
       }
     }
     return result
@@ -83,19 +102,57 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
             defaultValue={0}
             value={activeScore ? activeScore.score : 0}
             inputProps={{ style: { fontSize: 20 } }}
-            InputProps={{ disableUnderline: true, inputProps: { min: 0, max: questionMaxScore } }}
+            InputProps={{ disableUnderline: true, inputProps: { min: 0 } }}
             type="number"
             onChange={(event) => handleEditScore(+event.target.value, activeQuestion.id)}
           />
         )}
       </ScoreDisplayHeader>
-      <Typography variant="h6">Alla poäng: [ {scores.map((score) => `${score} `)}]</Typography>
-      <Typography variant="h6">Total poäng: {scores.reduce((a, b) => a + b, 0)}</Typography>
-
+      <Typography variant="h6">
+        Sidor:
+        <div style={{ display: 'flex' }}>
+          {questions.map((question, index) => (
+            <Card
+              key={index}
+              elevation={2}
+              style={{
+                width: 25,
+                height: 25,
+                display: 'flex',
+                alignItems: 'center',
+                justifyContent: 'center',
+                marginRight: 10,
+              }}
+            >
+              {index + 1}
+            </Card>
+          ))}
+        </div>
+        Poäng:
+        <div style={{ display: 'flex' }}>
+          {scores.map((score, index) => (
+            <Card
+              key={index}
+              elevation={2}
+              style={{
+                width: 25,
+                height: 25,
+                display: 'flex',
+                alignItems: 'center',
+                justifyContent: 'center',
+                marginRight: 10,
+              }}
+            >
+              {questions[index] ? score : '-'}
+            </Card>
+          ))}
+        </div>
+        Total poäng: {scores.reduce((a, b) => sumTwoScores(a, b), 0)}
+      </Typography>
       <AnswersDisplay>
         <Answers>
           <Divider />
-          <Typography variant="body1">Lagets svar:</Typography>
+          <UnderlinedTypography variant="body1">Lagets svar:</UnderlinedTypography>
           {activeQuestion && (
             <AnswerContainer>
               {getAnswers().map((v, k) => (
@@ -109,7 +166,9 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
 
         <Answers>
           <Divider />
-          <Typography variant="body1">Korrekta svar:</Typography>
+          {activeQuestion && activeQuestion.type_id !== 1 && (
+            <UnderlinedTypography variant="body1">Korrekta svar:</UnderlinedTypography>
+          )}
           {activeQuestion && (
             <AnswerContainer>
               {getAlternatives().map((v, k) => (
@@ -121,7 +180,6 @@ const JudgeScoreDisplay = ({ teamIndex, activeSlide }: ScoreDisplayProps) => {
           )}
         </Answers>
       </AnswersDisplay>
-
       {!activeQuestion && <Typography variant="body1">Inget svar</Typography>}
     </ScoreDisplayContainer>
   )
diff --git a/client/src/pages/views/components/JudgeScoringInstructions.tsx b/client/src/pages/views/components/JudgeScoringInstructions.tsx
index 86d8eaf1..f8c21667 100644
--- a/client/src/pages/views/components/JudgeScoringInstructions.tsx
+++ b/client/src/pages/views/components/JudgeScoringInstructions.tsx
@@ -8,7 +8,6 @@ type JudgeScoringInstructionsProps = {
 }
 
 const JudgeScoringInstructions = ({ question }: JudgeScoringInstructionsProps) => {
-  console.log(question)
   return (
     <JudgeScoringInstructionsContainer elevation={3}>
       <ScoringInstructionsInner>
diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx
index 6f4add03..bb65eca4 100644
--- a/client/src/pages/views/components/Timer.tsx
+++ b/client/src/pages/views/components/Timer.tsx
@@ -3,24 +3,36 @@ import { setPresentationTimer } from '../../../actions/presentation'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 
 type TimerProps = {
-  disableText?: boolean
+  variant: 'editor' | 'presentation'
 }
 
-const Timer = ({ disableText }: TimerProps) => {
+const Timer = ({ variant }: TimerProps) => {
   const dispatch = useAppDispatch()
   const timer = useAppSelector((state) => state.presentation.timer)
   const [remainingTimer, setRemainingTimer] = useState<number>(0)
+  const remainingSeconds = remainingTimer / 1000
+  const remainingWholeSeconds = Math.floor(remainingSeconds % 60)
+  // Add a 0 before the seconds if it's lower than 10
+  const remainingDisplaySeconds = `${remainingWholeSeconds < 10 ? '0' : ''}${remainingWholeSeconds}`
+
+  const remainingMinutes = Math.floor(remainingSeconds / 60) % 60
+  // Add a 0 before the minutes if it's lower than 10
+  const remainingDisplayMinutes = `${remainingMinutes < 10 ? '0' : ''}${remainingMinutes}`
+
+  const displayTime = `${remainingDisplayMinutes}:${remainingDisplaySeconds}`
   const [timerIntervalId, setTimerIntervalId] = useState<NodeJS.Timeout | null>(null)
-  const slideTimer = useAppSelector(
-    (state) =>
-      state.presentation.competition.slides.find((slide) => slide.id === state.presentation.activeSlideId)?.timer
-  )
+  const slideTimer = useAppSelector((state) => {
+    if (variant === 'presentation')
+      return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.activeSlideId)?.timer
+    return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)?.timer
+  })
 
   useEffect(() => {
-    if (slideTimer) setRemainingTimer(slideTimer)
+    if (slideTimer) setRemainingTimer(slideTimer * 1000)
   }, [slideTimer])
 
   useEffect(() => {
+    if (variant === 'editor') return
     if (!timer.enabled) {
       if (timerIntervalId !== null) clearInterval(timerIntervalId)
 
@@ -50,7 +62,7 @@ const Timer = ({ disableText }: TimerProps) => {
     )
   }, [timer.enabled, slideTimer])
 
-  return <>{`${!disableText ? 'Tid kvar:' : ''} ${Math.round(remainingTimer / 1000)}`}</>
+  return <>{slideTimer && displayTime}</>
 }
 
 export default Timer
diff --git a/client/src/pages/views/components/styled.tsx b/client/src/pages/views/components/styled.tsx
index e73b1463..602011d4 100644
--- a/client/src/pages/views/components/styled.tsx
+++ b/client/src/pages/views/components/styled.tsx
@@ -1,4 +1,4 @@
-import { Paper, TextField } from '@material-ui/core'
+import { Paper, TextField, Typography } from '@material-ui/core'
 import styled from 'styled-components'
 
 export const SlideContainer = styled.div`
@@ -61,3 +61,7 @@ export const Answers = styled.div`
   align-items: center;
   flex-direction: column;
 `
+
+export const UnderlinedTypography = styled(Typography)`
+  text-decoration: underline;
+`
diff --git a/client/src/utils/renderSlideIcon.tsx b/client/src/utils/renderSlideIcon.tsx
index 22f6405a..fe1d0040 100644
--- a/client/src/utils/renderSlideIcon.tsx
+++ b/client/src/utils/renderSlideIcon.tsx
@@ -3,6 +3,7 @@ import CheckBoxOutlinedIcon from '@material-ui/icons/CheckBoxOutlined'
 import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined'
 import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
 import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked'
+import UnfoldMoreOutlinedIcon from '@material-ui/icons/UnfoldMoreOutlined'
 import React from 'react'
 import { RichSlide } from '../interfaces/ApiRichModels'
 
@@ -17,6 +18,8 @@ export const renderSlideIcon = (slide: RichSlide) => {
         return <CheckBoxOutlinedIcon /> // multiple choice question
       case 4:
         return <RadioButtonCheckedIcon /> // single choice question
+      case 5:
+        return <UnfoldMoreOutlinedIcon /> // Match question
     }
   } else {
     return <InfoOutlinedIcon /> // information slide
diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py
index 1827b358..d3ae5d9d 100644
--- a/server/app/apis/alternatives.py
+++ b/server/app/apis/alternatives.py
@@ -3,11 +3,14 @@ All API calls concerning question alternatives.
 Default route: /api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives
 """
 
+from os import abort
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import QuestionAlternativeDTO
 from app.core.parsers import sentinel
+from app.database.models import Question, QuestionAlternative
 from flask_restx import Resource, reqparse
 
 api = QuestionAlternativeDTO.api
@@ -15,12 +18,14 @@ schema = QuestionAlternativeDTO.schema
 list_schema = QuestionAlternativeDTO.list_schema
 
 alternative_parser_add = reqparse.RequestParser()
-alternative_parser_add.add_argument("text", type=str, required=True, location="json")
-alternative_parser_add.add_argument("value", type=int, required=True, location="json")
+alternative_parser_add.add_argument("alternative", type=str, default="", location="json")
+alternative_parser_add.add_argument("correct", type=str, default="", location="json")
 
 alternative_parser_edit = reqparse.RequestParser()
-alternative_parser_edit.add_argument("text", type=str, default=sentinel, location="json")
-alternative_parser_edit.add_argument("value", type=int, default=sentinel, location="json")
+alternative_parser_edit.add_argument("alternative", type=str, default=sentinel, location="json")
+alternative_parser_edit.add_argument("alternative_order", type=int, default=sentinel, location="json")
+alternative_parser_edit.add_argument("correct", type=str, default=sentinel, location="json")
+alternative_parser_edit.add_argument("correct_order", type=int, default=sentinel, location="json")
 
 
 @api.route("")
@@ -77,6 +82,25 @@ class QuestionAlternatives(Resource):
             question_id,
             alternative_id,
         )
+
+        new_alternative_order = args.pop("alternative_order")
+        if new_alternative_order is not sentinel and item.alternative_order != new_alternative_order:
+            if not (0 <= new_alternative_order < dbc.utils.count(QuestionAlternative, {"question_id": question_id})):
+                abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_alternative_order}'")
+
+            item_question = dbc.get.one(Question, question_id)
+            dbc.utils.move_order(
+                item_question.alternatives, "alternative_order", item.alternative_order, new_alternative_order
+            )
+
+        new_correct_order = args.pop("correct_order")
+        if new_correct_order is not sentinel and item.correct_order != new_correct_order:
+            if not (0 <= new_correct_order < dbc.utils.count(QuestionAlternative, {"question_id": question_id})):
+                abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_correct_order}'")
+
+            item_question = dbc.get.one(Question, question_id)
+            dbc.utils.move_order(item_question.alternatives, "correct_order", item.correct_order, new_correct_order)
+
         item = dbc.edit.default(item, **args)
         return item_response(schema.dump(item))
 
diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index 552a0ad0..0f4aba74 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -97,7 +97,7 @@ class Statistics(Resource):
     def get(self):
         """ Gets statistics. """
 
-        user_count = User.query.count()
-        competition_count = Competition.query.count()
-        region_count = City.query.count()
+        user_count = dbc.utils.count(User)
+        competition_count = dbc.utils.count(Competition)
+        region_count = dbc.utils.count(City)
         return {"users": user_count, "competitions": competition_count, "regions": region_count}, http_codes.OK
diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
index 7f322d52..654ee49f 100644
--- a/server/app/apis/slides.py
+++ b/server/app/apis/slides.py
@@ -8,8 +8,7 @@ import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import SlideDTO
 from app.core.parsers import sentinel
-from app.database.controller.get import slide_count
-from app.database.models import Competition
+from app.database.models import Competition, Slide
 from flask_restx import Resource, reqparse
 from flask_restx.errors import abort
 
@@ -21,6 +20,7 @@ slide_parser_edit = reqparse.RequestParser()
 slide_parser_edit.add_argument("order", type=int, default=sentinel, location="json")
 slide_parser_edit.add_argument("title", type=str, default=sentinel, location="json")
 slide_parser_edit.add_argument("timer", type=int, default=sentinel, location="json")
+slide_parser_edit.add_argument("order", type=int, default=sentinel, location="json")
 slide_parser_edit.add_argument("background_image_id", default=sentinel, type=int, location="json")
 
 
@@ -59,6 +59,15 @@ class Slides(Resource):
         args = slide_parser_edit.parse_args(strict=True)
 
         item_slide = dbc.get.slide(competition_id, slide_id)
+
+        new_order = args.pop("order")
+        if new_order is not sentinel and item_slide.order != new_order:
+            if not (0 <= new_order < dbc.utils.count(Slide, {"competition_id": competition_id})):
+                abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_order}'")
+
+            item_competition = dbc.get.one(Competition, competition_id)
+            dbc.utils.move_order(item_competition.slides, "order", item_slide.order, new_order)
+
         item_slide = dbc.edit.default(item_slide, **args)
 
         return item_response(schema.dump(item_slide))
@@ -73,29 +82,6 @@ class Slides(Resource):
         return {}, codes.NO_CONTENT
 
 
-@api.route("/<slide_id>/order")
-@api.param("competition_id, slide_id")
-class SlideOrder(Resource):
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id, slide_id):
-        """ Edits the specified slide order using the provided arguments. """
-
-        args = slide_parser_edit.parse_args(strict=True)
-        new_order = args.get("order")
-
-        item_slide = dbc.get.slide(competition_id, slide_id)
-
-        if new_order == item_slide.order:
-            return item_response(schema.dump(item_slide))
-
-        if not (0 <= new_order < dbc.get.slide_count(competition_id)):
-            abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_order}'")
-
-        item_competition = dbc.get.one(Competition, competition_id)
-        dbc.utils.move_slides(item_competition, item_slide.order, new_order)
-        return item_response(schema.dump(item_slide))
-
-
 @api.route("/<slide_id>/copy")
 @api.param("competition_id,slide_id")
 class SlideCopy(Resource):
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index 64d9ca18..b5206d59 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -90,8 +90,10 @@ class QuestionAlternativeSchema(BaseSchema):
         model = models.QuestionAlternative
 
     id = ma.auto_field()
-    text = ma.auto_field()
-    value = ma.auto_field()
+    alternative = ma.auto_field()
+    alternative_order = ma.auto_field()
+    correct = ma.auto_field()
+    correct_order = ma.auto_field()
     question_id = ma.auto_field()
 
 
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 799fe4cc..70c2a8c6 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -5,8 +5,8 @@ This file contains functionality to add data to the database.
 import os
 
 import app.core.http_codes as codes
+import app.database.controller as dbc
 from app.core import db
-from app.database.controller import get, utils
 from app.database.models import (
     Blacklist,
     City,
@@ -80,7 +80,7 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, copy=False, *
         item.text = data.get("text")
     elif type_id == IMAGE_COMPONENT_ID:
         if not copy:  # Scale image if adding a new one, a copied image should keep it's size
-            item_image = get.one(Media, data["media_id"])
+            item_image = dbc.get.one(Media, data["media_id"])
             filename = item_image.filename
             path = os.path.join(
                 current_app.config["UPLOADED_PHOTOS_DEST"],
@@ -109,14 +109,14 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, copy=False, *
     else:
         abort(codes.BAD_REQUEST, f"Invalid type_id{type_id}")
 
-    item = utils.commit_and_refresh(item)
+    item = dbc.utils.commit_and_refresh(item)
     return item
 
 
 def code(view_type_id, competition_id=None, team_id=None):
     """ Adds a code to the database using the provided arguments. """
 
-    code_string = utils.generate_unique_code()
+    code_string = dbc.utils.generate_unique_code()
     return db_add(Code(code_string, view_type_id, competition_id, team_id))
 
 
@@ -135,7 +135,7 @@ def slide(competition_id):
     """ Adds a slide to the provided competition. """
 
     # Get the last order from given competition
-    order = Slide.query.filter(Slide.competition_id == competition_id).count()
+    order = dbc.utils.count(Slide, {"competition_id": competition_id})
 
     # Add slide
     item_slide = db_add(Slide(order, competition_id))
@@ -143,7 +143,7 @@ def slide(competition_id):
     # Add default question
     question(f"Fråga {item_slide.order + 1}", 10, 1, item_slide.id)
 
-    item_slide = utils.refresh(item_slide)
+    item_slide = dbc.utils.refresh(item_slide)
     return item_slide
 
 
@@ -151,12 +151,12 @@ def slide_without_question(competition_id):
     """ Adds a slide to the provided competition. """
 
     # Get the last order from given competition
-    order = Slide.query.filter(Slide.competition_id == competition_id).count()
+    order = dbc.utils.count(Slide, {"competition_id": competition_id})
 
     # Add slide
     item_slide = db_add(Slide(order, competition_id))
 
-    item_slide = utils.refresh(item_slide)
+    item_slide = dbc.utils.refresh(item_slide)
     return item_slide
 
 
@@ -179,7 +179,7 @@ def competition(name, year, city_id):
     # Add code for Operator view
     code(4, item_competition.id)
 
-    item_competition = utils.refresh(item_competition)
+    item_competition = dbc.utils.refresh(item_competition)
     return item_competition
 
 
@@ -202,7 +202,7 @@ def _competition_no_slides(name, year, city_id, font=None):
     # Add code for Operator view
     code(4, item_competition.id)
 
-    item_competition = utils.refresh(item_competition)
+    item_competition = dbc.utils.refresh(item_competition)
     return item_competition
 
 
@@ -276,13 +276,14 @@ def question(name, total_score, type_id, slide_id, correcting_instructions=None)
     return db_add(Question(name, total_score, type_id, slide_id, correcting_instructions))
 
 
-def question_alternative(text, value, question_id):
+def question_alternative(alternative, correct, question_id):
     """
     Adds a question alternative to the specified
     question using the provided arguments.
     """
 
-    return db_add(QuestionAlternative(text, value, question_id))
+    order = dbc.utils.count(QuestionAlternative, {"question_id": question_id})
+    return db_add(QuestionAlternative(alternative, order, correct, order, question_id))
 
 
 def question_score(score, question_id, team_id):
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 5c075b34..3c982eec 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -2,6 +2,7 @@
 This file contains functionality to get data from the database.
 """
 
+import app.database.controller as dbc
 from app.core import db
 from app.core import http_codes as codes
 from app.database.models import (
@@ -57,7 +58,7 @@ def code_list(competition_id):
 def user_exists(email):
     """ Checks if an user has that email. """
 
-    return User.query.filter(User.email == email).count() > 0
+    return dbc.utils.count(User, {"email": email}) > 0
 
 
 def user_by_email(email):
@@ -89,12 +90,6 @@ def slide_list(competition_id):
     return Slide.query.join(Competition, join_competition).filter(filters).all()
 
 
-def slide_count(competition_id):
-    """ Gets the number of slides in the provided competition. """
-
-    return Slide.query.filter(Slide.competition_id == competition_id).count()
-
-
 ### Teams ###
 def team(competition_id, team_id):
     """ Gets the team object associated with the competition and team. """
diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py
index 9662b08e..f71b61e7 100644
--- a/server/app/database/controller/utils.py
+++ b/server/app/database/controller/utils.py
@@ -9,14 +9,15 @@ from app.database.models import Code
 from flask_restx import abort
 
 
-def move_slides(item_competition, from_order, to_order):
+def move_order(orders, order_key, from_order, to_order):
     """
-    Move slide from from_order to to_order in item_competition.
+    Move key from from_order to to_order in db_item. See examples in
+    alternatives.py and slides.py.
     """
 
-    num_slides = len(item_competition.slides)
-    assert 0 <= from_order < num_slides, "Invalid order to move from"
-    assert 0 <= to_order < num_slides, "Invalid order to move to"
+    num_orders = len(orders)
+    assert 0 <= from_order < num_orders, "Invalid order to move from"
+    assert 0 <= to_order < num_orders, "Invalid order to move to"
 
     # This function is sooo terrible, someone please tell me how to update
     # multiple values in the database at the same time with unique constraints.
@@ -26,61 +27,75 @@ def move_slides(item_competition, from_order, to_order):
     # so 2 commits.
 
     # An example will follow the entire code to make it clear what it does
-    # Lets say we have 5 slides, and we want to move the slide at index 1
+    # Lets say we have 5 orders, and we want to move the item at index 1
     # to index 4.
-    # We begin with a list of slides with orders [0, 1, 2, 3, 4]
-
-    slides = item_competition.slides
+    # We begin with a list of item with orders [0, 1, 2, 3, 4]
 
     change = 1 if to_order < from_order else -1
     start_order = min(from_order, to_order)
     end_order = max(from_order, to_order)
 
-    # Move slides up 100
-    for item_slide in slides:
-        item_slide.order += 100
+    # Move orders up 100
+    for item_with_order in orders:
+        setattr(item_with_order, order_key, getattr(item_with_order, order_key) + 100)
 
-    # Our slide orders now look like [100, 101, 102, 103, 104]
+    # Our items now look like [100, 101, 102, 103, 104]
 
-    # Move slides between from and to order either up or down, but minus in front
-    for item_slide in slides:
-        if start_order <= item_slide.order - 100 <= end_order:
-            item_slide.order = -(item_slide.order + change)
+    # Move orders between from and to order either up or down, but minus in front
+    for item_with_order in orders:
+        if start_order <= getattr(item_with_order, order_key) - 100 <= end_order:
+            setattr(item_with_order, order_key, -(getattr(item_with_order, order_key) + change))
 
-    # Our slide orders now look like [100, -100, -101, -102, -103]
+    # Our items now look like [100, -100, -101, -102, -103]
 
-    # Find the slide that was to be moved and change it to correct order with minus in front
-    for item_slide in slides:
-        if item_slide.order == -(from_order + change + 100):
-            item_slide.order = -(to_order + 100)
+    # Find the item that was to be moved and change it to correct order with minus in front
+    for item_with_order in orders:
+        if getattr(item_with_order, order_key) == -(from_order + change + 100):
+            setattr(item_with_order, order_key, -(to_order + 100))
             break
 
-    # Our slide orders now look like [100, -104, -101, -102, -103]
+    # Our items now look like [100, -104, -101, -102, -103]
 
     db.session.commit()
 
     # Negate all order so that they become positive
-    for item_slide in slides:
-        if start_order <= -(item_slide.order + 100) <= end_order:
-            item_slide.order = -(item_slide.order)
+    for item_with_order in orders:
+        if start_order <= -(getattr(item_with_order, order_key) + 100) <= end_order:
+            setattr(item_with_order, order_key, -getattr(item_with_order, order_key))
 
-    # Our slide orders now look like [100, 104, 101, 102, 103]
+    # Our items now look like [100, 104, 101, 102, 103]
 
-    for item_slide in slides:
-        item_slide.order -= 100
+    for item_with_order in orders:
+        setattr(item_with_order, order_key, getattr(item_with_order, order_key) - 100)
 
-    # Our slide orders now look like [0, 4, 1, 2, 3]
+    # Our items now look like [0, 4, 1, 2, 3]
 
-    # We have now successfully moved slide 1 to 4
+    # We have now successfully moved item from order 1 to order 4
 
-    return commit_and_refresh(item_competition)
+    db.session.commit()
+
+
+def count(db_type, filter=None):
+    """
+    Count number of db_type items that match all keys and values in filter.
+
+    >>> count(User, {"city_id": 1}) # Get number of users with city_id equal to 1
+    5
+    """
+
+    filter = filter or {}
+    query = db_type.query
+    for key, value in filter.items():
+        query = query.filter(getattr(db_type, key) == value)
+    return query.count()
 
 
 def generate_unique_code():
     """ Generates a unique competition code. """
 
     code = generate_code_string()
-    while db.session.query(Code).filter(Code.code == code).count():
+
+    while count(Code, {"code": code}):
         code = generate_code_string()
     return code
 
diff --git a/server/app/database/models.py b/server/app/database/models.py
index f46e1cd1..7596ab6b 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -5,7 +5,8 @@ each other.
 """
 
 from app.core import bcrypt, db
-from app.database.types import IMAGE_COMPONENT_ID, QUESTION_COMPONENT_ID, TEXT_COMPONENT_ID
+from app.database.types import (IMAGE_COMPONENT_ID, QUESTION_COMPONENT_ID,
+                                TEXT_COMPONENT_ID)
 from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
 from sqlalchemy.orm import backref
 
@@ -241,15 +242,23 @@ class QuestionAlternative(db.Model):
 
     Depend on table: Question.
     """
+    __table_args__ = (
+        db.UniqueConstraint("question_id", "alternative_order"),
+        db.UniqueConstraint("question_id", "correct_order"),
+    )
 
     id = db.Column(db.Integer, primary_key=True)
-    text = db.Column(db.String(STRING_SIZE), nullable=False)
-    value = db.Column(db.Integer, nullable=False)
+    alternative = db.Column(db.String(STRING_SIZE), nullable=False)
+    alternative_order = db.Column(db.Integer)
+    correct = db.Column(db.String(STRING_SIZE), nullable=False)
+    correct_order = db.Column(db.Integer)
     question_id = db.Column(db.Integer, db.ForeignKey("question.id"), nullable=False)
 
-    def __init__(self, text, value, question_id):
-        self.text = text
-        self.value = value
+    def __init__(self, alternative, alternative_order, correct, correct_order, question_id):
+        self.alternative = alternative
+        self.alternative_order = alternative_order
+        self.correct = correct
+        self.correct_order = correct_order
         self.question_id = question_id
 
 
diff --git a/server/populate.py b/server/populate.py
index 34b05347..9ea096cd 100644
--- a/server/populate.py
+++ b/server/populate.py
@@ -11,7 +11,7 @@ from app.database.models import City, QuestionType, Role
 
 def create_default_items():
     media_types = ["Image", "Video"]
-    question_types = ["Text", "Practical", "Multiple", "Single"]
+    question_types = ["Text", "Practical", "Multiple", "Single", "Match"]
     component_types = ["Text", "Image", "Question"]
     view_types = ["Team", "Judge", "Audience", "Operator"]
 
@@ -77,7 +77,7 @@ def create_default_items():
             """
 
             for k in range(3):
-                dbc.add.question_alternative(f"Alternative {k}", 0, item_slide.questions[0].id)
+                dbc.add.question_alternative(f"Alternative {k}", f"Correct {k}", item_slide.questions[0].id)
 
             # Add text components
             # TODO: Add images as components
diff --git a/server/tests/test_db.py b/server/tests/test_db.py
index 0e028746..649ebbf1 100644
--- a/server/tests/test_db.py
+++ b/server/tests/test_db.py
@@ -193,21 +193,26 @@ def test_move_slides(client):
         dbc.add.slide(item_comp.id)
 
     # Move from beginning to end
-    item_comp = dbc.utils.move_slides(item_comp, 0, 9)
+    dbc.utils.move_order(item_comp.slides, "order", 0, 9)
+    dbc.utils.refresh(item_comp)
     assert_slide_order(item_comp, [9, 0, 1, 2, 3, 4, 5, 6, 7, 8])
 
     # Move from end to beginning
-    item_comp = dbc.utils.move_slides(item_comp, 9, 0)
+    dbc.utils.move_order(item_comp.slides, "order", 9, 0)
+    dbc.utils.refresh(item_comp)
     assert_slide_order(item_comp, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
 
     # Move some things in the middle
-    item_comp = dbc.utils.move_slides(item_comp, 3, 7)
+    dbc.utils.move_order(item_comp.slides, "order", 3, 7)
+    dbc.utils.refresh(item_comp)
     assert_slide_order(item_comp, [0, 1, 2, 7, 3, 4, 5, 6, 8, 9])
 
-    item_comp = dbc.utils.move_slides(item_comp, 1, 5)
+    dbc.utils.move_order(item_comp.slides, "order", 1, 5)
+    dbc.utils.refresh(item_comp)
     assert_slide_order(item_comp, [0, 5, 1, 7, 2, 3, 4, 6, 8, 9])
 
-    item_comp = dbc.utils.move_slides(item_comp, 8, 2)
+    dbc.utils.move_order(item_comp.slides, "order", 8, 2)
+    dbc.utils.refresh(item_comp)
     assert_slide_order(item_comp, [0, 6, 1, 8, 3, 4, 5, 7, 2, 9])
 
 
-- 
GitLab