Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix badges disappearing randomly #1671

Merged
merged 12 commits into from
Jun 16, 2022
234 changes: 116 additions & 118 deletions Pipfile.lock

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions game_frontend/src/components/Badge/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ const BadgeModalImg = styled(Box)`
export default class BadgeModal extends Component {
static propTypes = {
modalOpen: PropTypes.bool,
taskId: PropTypes.string,
badgeId: PropTypes.string,
}

render() {
if (!this.props.modalOpen) {
return null
}

const taskId = this.props.taskId
const info = badgeInfo[taskId]
const badgeId = this.props.badgeId
const info = badgeInfo[badgeId]
if (info === undefined) {
return null
}
Expand All @@ -62,15 +62,15 @@ export default class BadgeModal extends Component {
}
}

export function getBadges(tasks) {
return tasks.map((task) => (
export function getBadges(badges) {
return badges.map((badge) => (
<Box
component="img"
style={{ height: 45, marginRight: 15 }}
alt={badgeInfo[task].name}
title={badgeInfo[task].name}
src={badgeInfo[task].img}
key={task}
alt={badgeInfo[badge].name}
title={badgeInfo[badge].name}
src={badgeInfo[badge].img}
key={badge}
/>
))
}
36 changes: 22 additions & 14 deletions game_frontend/src/components/NavigationBar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ export class NavigationBar extends Component {
static propTypes = {
// the props received from redux state or reducers
modalOpen: PropTypes.bool,
completedTasks: PropTypes.string,
completedBadges: PropTypes.string,
badgesInit: PropTypes.func,
gameState: PropTypes.any,
}

state = { modalOpen: false, completedTasks: [], lastTask: '' }
state = { modalOpen: false, completedBadges: [], lastBadge: '' }

componentDidMount() {
this.props.badgesInit()
Expand All @@ -56,27 +57,33 @@ export class NavigationBar extends Component {

static getDerivedStateFromProps(props, state) {
// Any time completedTasks change, pass the new info as state
if (props.completedTasks !== undefined) {
// convert to string for comparison
const stateTasksString = state.completedTasks.join() + ','
if (props.completedBadges !== undefined && props.gameState !== undefined) {
const worksheetID = props.gameState.worksheetID
let badges = props.completedBadges.split(',')
badges = badges.filter((s) => s) // remove empty element
// remove any badge that's not relevant to the current worksheet
badges = badges.filter((b) => {
return b.startsWith(worksheetID + ':')
})

if (props.completedTasks !== stateTasksString) {
let newTasks = props.completedTasks.split(',')
newTasks = newTasks.filter((s) => s) // remove empty element
const lastTask = newTasks[newTasks.length - 1] // assume the last element is the last task
// convert to string for comparison
const stateBadgesString = state.completedBadges.join() + ','

if (props.completedBadges !== stateBadgesString) {
const lastBadge = badges[badges.length - 1] // assume the last element is the last badge
// return badge info with popup if there is a new badge earned
return {
modalOpen: props.modalOpen,
completedTasks: newTasks,
lastTask: lastTask,
completedBadges: badges,
lastBadge: lastBadge,
}
}
}
return null // no change
}

renderLogoToolbar = () => {
const badges = getBadges(this.state.completedTasks)
const badges = getBadges(this.state.completedBadges)
return (
<LogoToolbar>
<IconButton href={urlForAimmoDashboard} aria-label="Kurono dashboard" color="inherit">
Expand Down Expand Up @@ -109,15 +116,16 @@ export class NavigationBar extends Component {
{this.renderLogoToolbar()}
{this.renderButtonToolbar()}
</KuronoAppBar>
<BadgeModal taskId={this.state.lastTask} modalOpen={this.state.modalOpen} />
<BadgeModal badgeId={this.state.lastBadge} modalOpen={this.state.modalOpen} />
</NavigationBarLayout>
)
}
}

const mapStateToProps = (state) => ({
completedTasks: state.avatarWorker.completedTasks,
completedBadges: state.avatarWorker.completedBadges,
modalOpen: state.avatarWorker.modalOpen,
gameState: state.game.gameState,
})

const mapDispatchToProps = {
Expand Down
68 changes: 20 additions & 48 deletions game_frontend/src/pyodide/badges.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
/* eslint-env jest */
import { checkIfBadgeEarned, filterByWorksheet } from './badges'
import { checkIfBadgeEarned } from './badges'
jest.mock('threads/worker')

describe('Badges check', () => {
it('awards badge 1 if the right conditions are met', () => {
const badges = ''
const turnResult = {
action: {
action_type: "move",
action_type: 'move',
options: {
direction: {x: 0, y: -1}
}
direction: { x: 0, y: -1 },
},
},
log: "",
log: '',
turnCount: 1,
}
const userCode = ''
const gameState = {worksheetID: 1}
const gameState = { worksheetID: 1 }
const playerAvatarId = 1

const result = checkIfBadgeEarned(badges, turnResult, userCode, gameState, playerAvatarId)

const expected = "1:1,"
const expected = '1:1,'

expect(result).toBe(expected)
})
Expand All @@ -30,12 +30,12 @@ describe('Badges check', () => {
const badges = ''
const turnResult = {
action: {
action_type: "move",
action_type: 'move',
options: {
direction: {x: 0, y: -1}
}
direction: { x: 0, y: -1 },
},
},
log: "",
log: '',
turnCount: 1,
}
const userCode = `
Expand All @@ -56,12 +56,12 @@ def next_turn(world_state, avatar_state):

return action
`
const gameState = {worksheetID: 1}
const gameState = { worksheetID: 1 }
const playerAvatarId = 1

const result = checkIfBadgeEarned(badges, turnResult, userCode, gameState, playerAvatarId)

const expected = "1:1,1:2,"
const expected = '1:1,1:2,'

expect(result).toBe(expected)
})
Expand All @@ -70,12 +70,12 @@ def next_turn(world_state, avatar_state):
const badges = ''
const turnResult = {
action: {
action_type: "move",
action_type: 'move',
options: {
direction: {x: 0, y: -1}
}
direction: { x: 0, y: -1 },
},
},
log: "",
log: '',
turnCount: 1,
}
const userCode = `
Expand Down Expand Up @@ -108,44 +108,16 @@ def next_turn(world_state, avatar_state):
players: [
{
id: 1,
location: {x: 10, y: 10}
}
location: { x: 10, y: 10 },
},
],
obstacles: [],
}
const playerAvatarId = 1

const result = checkIfBadgeEarned(badges, turnResult, userCode, gameState, playerAvatarId)

const expected = "1:1,1:2,1:3,"

expect(result).toBe(expected)
})

it('filters badges', () => {
const badges = '1:1,1:2,1:3,2:1,2:2,3:1'

let gameState = {worksheetID: 1}

let result = filterByWorksheet(badges, gameState)

let expected = "1:1,1:2,1:3"

expect(result).toBe(expected)

gameState = {worksheetID: 2}

result = filterByWorksheet(badges, gameState)

expected = "2:1,2:2"

expect(result).toBe(expected)

gameState = {worksheetID: 3}

result = filterByWorksheet(badges, gameState)

expected = "3:1"
const expected = '1:1,1:2,1:3,'

expect(result).toBe(expected)
})
Expand Down
36 changes: 6 additions & 30 deletions game_frontend/src/pyodide/badges.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
/* eslint-env worker */
import ComputedTurnResult from './computedTurnResult'

export function filterByWorksheet(badges: any, gameState: any): string {
const worksheetID = gameState.worksheetID
let badgesArr = badges.split(',')

badgesArr = badgesArr.filter((s) => s) // remove empty element
// remove any badge that's not relevant to the current worksheet
badgesArr = badgesArr.filter((b) => {
return b.startsWith(worksheetID + ':')
})

return badgesArr.join(',')
}

export function checkIfBadgeEarned(
badges: string,
result: ComputedTurnResult,
userCode: string,
gameState: any,
playerAvatarId: number
gameState: any
): string {
const userPythonCode = userCode.replace(/\s*#.*/gm, '') // Remove all comment lines from the user's code
const badgesPerWorksheet = [
Expand All @@ -28,7 +14,7 @@ export function checkIfBadgeEarned(
{
id: 3,
worksheetID: 1,
trigger: badge3Trigger(result, userPythonCode, gameState, playerAvatarId),
trigger: badge3Trigger(result, userPythonCode),
},
]

Expand All @@ -40,7 +26,7 @@ export function checkIfBadgeEarned(
badge.trigger
) {
// Here is when a new badge is earned
// TODO on worksshet 2: This might have to order the badges, in case user does not do the worksheet in order
// TODO on worksheet 2: This might have to order the badges, in case user does not do the worksheet in order
badges += `${badgeWorksheetPair},`
}
}
Expand Down Expand Up @@ -71,21 +57,11 @@ function badge2Trigger(userPythonCode: string): boolean {
return substrings.every((substring) => userPythonCode.includes(substring))
}

function badge3Trigger(
result: any,
userPythonCode: string,
gameState: any,
playerAvatarId: number
): boolean {
function badge3Trigger(result: any, userPythonCode: string): boolean {
// Check the code contains certain keywords about moving to a cell
const substrings = ['world_state.can_move_to(', 'print(', 'if ']
const codeContainsKeywords = substrings.every((substring) => userPythonCode.includes(substring))

if (!codeContainsKeywords) return false

// Check action is move action
const isMoveAction = result.action.action_type === 'move'
if (!isMoveAction) return false

return true
// And check it returns a move action
return codeContainsKeywords && result.action.action_type === 'move'
}
9 changes: 2 additions & 7 deletions game_frontend/src/pyodide/pyodideRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,13 @@ async function initializePyodideWorker() {
await worker.initializePyodide()
}

export async function filterByWorksheet(badges: string, gameState: any): Promise<string> {
return worker.filterByWorksheet(badges, gameState)
}

export async function checkIfBadgeEarned(
badges: string,
result: ComputedTurnResult,
userCode: string,
gameState: any,
playerAvatarId: number
gameState: any
): Promise<string> {
return worker.checkIfBadgeEarned(badges, result, userCode, gameState, playerAvatarId)
return worker.checkIfBadgeEarned(badges, result, userCode, gameState)
}

export async function updateAvatarCode(
Expand Down
6 changes: 0 additions & 6 deletions game_frontend/src/redux/features/AvatarWorker/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ const getBadgesRequest = () => ({
type: types.GET_BADGES_REQUEST,
})

const filterBadges = (badges) => ({
type: types.FILTER_BADGES,
payload: badges,
})

const getBadgesReceived = (badges) => ({
type: types.GET_BADGES_SUCCESS,
payload: badges,
Expand Down Expand Up @@ -61,7 +56,6 @@ export default {
avatarCodeUpdated,
avatarsNextActionComputed,
badgesEarned,
filterBadges,
getBadgesRequest,
getBadgesReceived,
checkBadgesReceived,
Expand Down
Loading