Merge pull request #1298 from pnp/react-word-game-2

This commit is contained in:
Hugo Bernier 2020-05-28 22:52:19 -04:00 committed by GitHub
commit f2027a9dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 510 additions and 444 deletions

View File

@ -8,28 +8,24 @@ import {
PropertyPaneCheckbox PropertyPaneCheckbox
} from '@microsoft/sp-webpart-base'; } from '@microsoft/sp-webpart-base';
import * as strings from 'WordGameWebPartStrings';
import WordGame from './components/WordGame'; import WordGame from './components/WordGame';
import { IWordGameProps } from './components/IWordGameProps'; import { IWordGameProps } from './components/IWordGameProps';
import { WordService } from './components/WordService';
export interface IWordGameWebPartProps { export interface IWordGameWebPartProps {
gameTitle: string; gameTitle: string;
enableHighScores: boolean enableHighScores: boolean;
} }
export default class WordGameWebPart extends BaseClientSideWebPart<IWordGameWebPartProps> { export default class WordGameWebPart extends BaseClientSideWebPart<IWordGameWebPartProps> {
wordGame: WordGame; public render(): void {
constructor() { if (!this.properties.gameTitle) {
super(); this.properties.gameTitle = 'Word Game';
} }
public render(): void { if (this.properties.enableHighScores === undefined) {
if (!this.properties.gameTitle) this.properties.enableHighScores = true;
this.properties.gameTitle='Word Game'; }
if (this.properties.enableHighScores==null)
this.properties.enableHighScores=true;
const element: React.ReactElement<IWordGameProps> = React.createElement( const element: React.ReactElement<IWordGameProps> = React.createElement(
WordGame, WordGame,
@ -40,9 +36,7 @@ export default class WordGameWebPart extends BaseClientSideWebPart<IWordGameWebP
} }
); );
ReactDom.render(element, this.domElement); ReactDom.render(element, this.domElement);
} }
protected onDispose(): void { protected onDispose(): void {
@ -53,7 +47,7 @@ export default class WordGameWebPart extends BaseClientSideWebPart<IWordGameWebP
return Version.parse('1.0'); return Version.parse('1.0');
} }
//only refresh render after applying settings pane // only refresh render after applying settings pane
protected get disableReactivePropertyChanges(): boolean { protected get disableReactivePropertyChanges(): boolean {
return true; return true;
} }
@ -74,7 +68,7 @@ export default class WordGameWebPart extends BaseClientSideWebPart<IWordGameWebP
}), }),
PropertyPaneTextField('gameTitle', { PropertyPaneTextField('gameTitle', {
label: 'Game Title' label: 'Game Title'
}), })
] ]
} }
] ]

View File

@ -1,6 +1,6 @@
import { WordGameListItem } from "./WordService"; import { WordGameListItem } from './WordService';
import { GameState } from "./WordGame"; import { GameState } from './WordGame';
import { WebPartContext } from "@microsoft/sp-webpart-base"; import { WebPartContext } from '@microsoft/sp-webpart-base';
export interface IWordGameProps { export interface IWordGameProps {
description: string; description: string;
@ -19,5 +19,4 @@ export interface IWordGameState {
lblMessage: string; lblMessage: string;
highScores: WordGameListItem[]; highScores: WordGameListItem[];
mobileMode: boolean; mobileMode: boolean;
} }

View File

@ -1,11 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import styles from './WordGame.module.scss'; import styles from './WordGame.module.scss';
import { IWordGameProps, IWordGameState } from './IWordGameProps'; import { IWordGameProps, IWordGameState } from './IWordGameProps';
import { escape, round } from '@microsoft/sp-lodash-subset';
import { WordService, Game } from './WordService'; import { WordService, Game } from './WordService';
import { Ref } from 'react';
import { WordGameListItem } from './WordService';
import WordHighScores from './WordHighScores'; import WordHighScores from './WordHighScores';
import { WordGameListItem } from '../../../../lib/webparts/wordGame/components/WordService';
// tslint:disable-next-line: no-any
const logo: any = require('../assets/ajax-loader.gif'); const logo: any = require('../assets/ajax-loader.gif');
require('./anims.css'); require('./anims.css');
@ -17,13 +17,7 @@ export enum GameState {
} }
export default class WordGame extends React.Component<IWordGameProps, IWordGameState> { export default class WordGame extends React.Component<IWordGameProps, IWordGameState> {
public state: IWordGameState =
wordService: WordService = new WordService();
currentGame: Game;
timerInterval: number = -1;
numTimer: number;
state: IWordGameState =
{ {
gamestate: GameState.GameLoading, gamestate: GameState.GameLoading,
currentWord: '', currentWord: '',
@ -37,6 +31,12 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
mobileMode: false mobileMode: false
}; };
private messageCounter: number = 0;
private wordService: WordService = new WordService();
private currentGame: Game;
private timerInterval: number = -1;
private numTimer: number;
constructor() { constructor() {
super(); super();
@ -44,183 +44,24 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
} }
async componentDidMount() { public async componentDidMount(): Promise<void> {
this.wordService.SetContext(this.props.context); this.wordService.SetContext(this.props.context);
await this.wordService.loadWords(); await this.wordService.loadWords();
if (window.innerWidth<600) if (window.innerWidth < 600) {
this.setState({mobileMode:true}) this.setState({ mobileMode: true });
}
this.setState({ this.setState({
gamestate: GameState.Title, gamestate: GameState.Title
} as IWordGameState); } as IWordGameState);
this.getHighScores(); this.getHighScores();
}
public async redraw() {
this.forceUpdate();
}
answerChanged(event) {
this.setState({ txtAnswer: event.target.value } as IWordGameState);
}
messageCounter = 0;
showMessage(message: string) {
this.setState({ lblMessage: message } as IWordGameState);
this.messageCounter = 3;
}
async btnAnswer() {
let answerFound: boolean = false;
this.currentGame.rounds[this.state.currentRound].answers.forEach(answer => {
if (answer.toLowerCase() == this.state.txtAnswer.toLowerCase()) {
this.currentGame.rounds[this.state.currentRound].correctAnswer = this.state.txtAnswer;
this.numTimer += 5;
this.showMessage('Correct!! +5 Seconds');
this.setState({
lblTimer: this.numTimer + " seconds",
score: this.state.score + 1
} as IWordGameState);
answerFound = true;
}
})
if (!answerFound) {
this.showMessage('Incorrect');
this.currentGame.rounds[this.state.currentRound].incorrectAnswers.push(this.state.txtAnswer);
this.setState({ txtAnswer: '' } as IWordGameState);
this.focusOnTextbox();
}
else {
this.nextRound();
}
}
btnSkip() {
this.nextRound();
this.focusOnTextbox();
}
async nextRound() {
//state doesn't modify immediately
await this.setState({
txtAnswer: '',
currentRound: this.state.currentRound + 1,
} as IWordGameState);
if (this.state.currentRound >= this.currentGame.rounds.length) {
this.finishGame();
}
else {
this.setState({
currentWord: this.currentGame.rounds[this.state.currentRound].word,
possibleAnswersText: this.getPossibleAnswersText(this.state.currentRound)
} as IWordGameState);
}
}
getPossibleAnswersText(currentRound:number):string{
let possibleAnswers = this.currentGame.rounds[currentRound].answers.length;
let possibleAnswersText = possibleAnswers + ' Possible Answer';
if (possibleAnswers>1)
possibleAnswersText = possibleAnswers + ' Possible Answers';
return possibleAnswersText
}
focusOnTextbox() {
let element = document.getElementById('txtWordGameAnswer');
if (element)
element.focus();
}
finishGame() {
this.sendScore(this.state.score, this.numTimer, JSON.stringify(this.currentGame));
this.stopTimer();
this.setState({ gamestate: GameState.GameFinished } as IWordGameState);
}
async sendScore(score: number, seconds: number, details: string) {
if (!this.props.enableHighScores)
return;
await this.wordService.SubmitScore(score, seconds, details);
this.getHighScores();
}
async getHighScores(){
if (!this.props.enableHighScores)
return;
let scores = await this.wordService.GetHighScores();
this.setState({
highScores: scores
} as IWordGameState);
}
keyDownAnswer(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.keyCode == 13)
this.btnAnswer();
}
async btnStartGame() {
this.setState({
gamestate: GameState.GameLoading
} as IWordGameState);
this.currentGame = this.wordService.GenerateGame();
await new Promise<void>(resolve => { setTimeout(resolve, 1000); });
this.setState({
gamestate: GameState.GamePlaying,
currentRound: 0,
txtAnswer: '',
score: 0,
lblMessage: '',
currentWord: this.currentGame.rounds[0].word,
possibleAnswersText: this.getPossibleAnswersText(0)
} as IWordGameState);
this.numTimer = 30;
this.setState({ lblTimer: this.numTimer + " seconds" });
this.timerInterval = setInterval(this.timerTick.bind(this), 1000);
}
timerTick() {
//for debugging
// if (this.numTimer>25)
this.numTimer--;
this.setState({ lblTimer: this.numTimer + " seconds" });
if (this.numTimer == 0) {
this.finishGame();
}
//toast messages
if (this.messageCounter > 0) {
this.messageCounter--;
if (this.messageCounter == 0)
this.setState({ lblMessage: '' } as IWordGameState);
}
}
stopTimer() {
this.setState({ lblTimer: '' });
clearInterval(this.timerInterval);
} }
public render(): React.ReactElement<IWordGameProps> { public render(): React.ReactElement<IWordGameProps> {
window["wordgame"] = this; // tslint:disable-next-line: no-string-literal
window['wordgame'] = this;
let body: JSX.Element; let body: JSX.Element;
switch (this.state.gamestate) { switch (this.state.gamestate) {
@ -231,70 +72,91 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
{/* <h1>Word Game</h1> */} {/* <h1>Word Game</h1> */}
{/* <h4>Count: {this.wordService.GetWordCount()}</h4> */} {/* <h4>Count: {this.wordService.GetWordCount()}</h4> */}
<p>Unscramble as many words as you can before the time runs out</p> <p>Unscramble as many words as you can before the time runs out</p>
<button className="blueButton" onClick={this.btnStartGame.bind(this)}> <button className='blueButton' onClick={this.btnStartGame.bind(this)}>
Start Game Start Game
</button> </button>
{ {
this.props.enableHighScores ? this.props.enableHighScores ?
<WordHighScores scores={this.state.highScores}></WordHighScores> : '' <WordHighScores scores={this.state.highScores}></WordHighScores> : ''
} }
</div> </div>;
break; break;
case GameState.GameLoading: case GameState.GameLoading:
body = body =
<div> <div>
<img src={logo} /> <img src={logo} alt='Loading...' />
<span style={{ marginLeft: '15px' }}> <span style={{ marginLeft: '15px' }}>
Loading... Loading...
</span> </span>
</div> </div>;
break; break;
case GameState.GamePlaying: case GameState.GamePlaying:
body = body =
<div> <div>
<div className="currentWord" style={{position:"relative"}}> <div className='currentWord' style={{ position: 'relative' }}>
{this.state.currentWord} {this.state.currentWord}
{/* MOBILE TOAST */} {/* MOBILE TOAST */}
{ {
this.state.lblMessage != '' && this.state.mobileMode? this.state.lblMessage !== '' && this.state.mobileMode ?
<div className={this.state.lblMessage == 'Incorrect' ? 'toastMiniRed word-fade-in' : 'toastMiniGreen word-fade-in'} <div className={this.state.lblMessage === 'Incorrect' ?
style={{position:"absolute",left:"0",right:"0",fontSize:"10pt",top:"83px",padding:"0"}}> 'toastMiniRed word-fade-in'
: 'toastMiniGreen word-fade-in'}
style={{
position: 'absolute',
left: '0',
right: '0',
fontSize: '10pt',
top: '83px',
padding: '0'
}}>
{ {
this.state.lblMessage this.state.lblMessage
} }
</div> : '' </div> : ''
} }
</div> </div>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0"></meta> <meta name='viewport'
<input type="text" autoFocus={true} placeholder="Enter Answer" id="txtWordGameAnswer" content='width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0'></meta>
autoComplete="off" autoCapitalize="off" autoCorrect="off" spellCheck={false} <input
value={this.state.txtAnswer} onChange={this.answerChanged.bind(this)} onKeyDown={this.keyDownAnswer.bind(this)} /> type='text'
<div className="numAnswersTip">{this.state.possibleAnswersText}</div> autoFocus={true}
placeholder='Enter Answer'
id='txtWordGameAnswer'
autoComplete='off'
autoCapitalize='off'
autoCorrect='off'
spellCheck={false}
value={this.state.txtAnswer}
onChange={this.answerChanged.bind(this)}
onKeyDown={this.keyDownAnswer.bind(this)} />
<div className='numAnswersTip'>{this.state.possibleAnswersText}</div>
<table style={{ marginLeft: 'Auto', marginRight: 'Auto' }}> <table style={{ marginLeft: 'Auto', marginRight: 'Auto' }}>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<button className="blueButton" style={{ marginTop: '20px' }} onClick={this.btnAnswer.bind(this)}> <button className='blueButton' style={{ marginTop: '20px' }} onClick={this.btnAnswer.bind(this)}>
Submit Submit
</button> </button>
</td> </td>
<td> <td>
<button className="greyButton" onClick={this.btnSkip.bind(this)} style={{ marginTop: '20px' }} > <button className='greyButton' onClick={this.btnSkip.bind(this)} style={{ marginTop: '20px' }} >
Skip Skip
</button> </button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div className="timer">{this.state.lblTimer}</div> <div className='timer'>{this.state.lblTimer}</div>
{/* DESKTOP TOAST */} {/* DESKTOP TOAST */}
{ {
this.state.lblMessage != '' && !this.state.mobileMode? this.state.lblMessage !== '' && !this.state.mobileMode ?
<div className="toast word-fade-in" > <div className='toast word-fade-in' >
<div className={this.state.lblMessage == 'Incorrect' ? 'toastRed' : 'toastGreen'}>{this.state.lblMessage}</div> <div
className={this.state.lblMessage === 'Incorrect'
? 'toastRed' : 'toastGreen'}>{this.state.lblMessage}</div>
</div> : '' </div> : ''
} }
</div> </div>;
break; break;
case GameState.GameFinished: case GameState.GameFinished:
@ -302,9 +164,9 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
<div> <div>
<h1>Well done! Score {this.state.score} out of 6</h1> <h1>Well done! Score {this.state.score} out of 6</h1>
<p>See answers below to the current round</p> <p>See answers below to the current round</p>
<button className="blueButton" onClick={this.btnStartGame.bind(this)} <button className='blueButton' onClick={this.btnStartGame.bind(this)}
style={{ marginBottom: '20px' }}>Play Again</button> style={{ marginBottom: '20px' }}>Play Again</button>
<div className="gameReview"> <div className='gameReview'>
<ul> <ul>
{ {
this.currentGame.rounds.map(round => ( this.currentGame.rounds.map(round => (
@ -314,9 +176,19 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
<ul key={answer + '_wordgame_answer'}> <ul key={answer + '_wordgame_answer'}>
<b>Answer </b> {answer} <b>Answer </b> {answer}
{ {
round.correctAnswer == answer ? round.correctAnswer === answer ?
<svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px' }} width="15" height="15" viewBox="0 0 24 24" color="#155724"> <svg
<path fill="currentcolor" d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z" /></svg> xmlns='http://www.w3.org/2000/svg'
style={{
marginLeft: '5px'
}}
width='15'
height='15'
viewBox='0 0 24 24'
color='#155724'>
<path
fill='currentcolor'
d='M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z' /></svg>
: '' : ''
} }
</ul> </ul>
@ -331,13 +203,12 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
this.props.enableHighScores ? this.props.enableHighScores ?
<WordHighScores scores={this.state.highScores}></WordHighScores> : '' <WordHighScores scores={this.state.highScores}></WordHighScores> : ''
} }
</div> </div>;
break; break;
} }
let maindiv = <div className="wordGameCustom" style={{ position: 'relative' }}>{body}</div>; const maindiv: JSX.Element = <div className='wordGameCustom' style={{ position: 'relative' }}>{body}</div>;
return ( return (
<div className={styles.wordGame} style={{ textAlign: 'center' }} > <div className={styles.wordGame} style={{ textAlign: 'center' }} >
@ -346,5 +217,159 @@ export default class WordGame extends React.Component<IWordGameProps, IWordGameS
); );
} }
// private async redraw(): Promise<void> {
// this.forceUpdate();
// }
private answerChanged(event: React.ChangeEvent<HTMLInputElement>): void {
this.setState({ txtAnswer: event.target.value } as IWordGameState);
}
private showMessage(message: string): void {
this.setState({ lblMessage: message } as IWordGameState);
this.messageCounter = 3;
}
private async btnAnswer(): Promise<void> {
let answerFound: boolean = false;
this.currentGame.rounds[this.state.currentRound].answers.forEach(answer => {
if (answer.toLowerCase() === this.state.txtAnswer.toLowerCase()) {
this.currentGame.rounds[this.state.currentRound].correctAnswer = this.state.txtAnswer;
this.numTimer += 5;
this.showMessage('Correct!! +5 Seconds');
this.setState({
lblTimer: this.numTimer + ' seconds',
score: this.state.score + 1
} as IWordGameState);
answerFound = true;
}
});
if (!answerFound) {
this.showMessage('Incorrect');
this.currentGame.rounds[this.state.currentRound].incorrectAnswers.push(this.state.txtAnswer);
this.setState({ txtAnswer: '' } as IWordGameState);
this.focusOnTextbox();
} else {
this.nextRound();
}
}
private btnSkip(): void {
this.nextRound();
this.focusOnTextbox();
}
private async nextRound(): Promise<void> {
// state doesn't modify immediately
await this.setState({
txtAnswer: '',
currentRound: this.state.currentRound + 1
} as IWordGameState);
if (this.state.currentRound >= this.currentGame.rounds.length) {
this.finishGame();
} else {
this.setState({
currentWord: this.currentGame.rounds[this.state.currentRound].word,
possibleAnswersText: this.getPossibleAnswersText(this.state.currentRound)
} as IWordGameState);
}
}
private getPossibleAnswersText(currentRound: number): string {
const possibleAnswers: number = this.currentGame.rounds[currentRound].answers.length;
let possibleAnswersText: string = possibleAnswers + ' Possible Answer';
if (possibleAnswers > 1) {
possibleAnswersText = possibleAnswers + ' Possible Answers';
}
return possibleAnswersText;
}
private focusOnTextbox(): void {
const element: HTMLElement = document.getElementById('txtWordGameAnswer');
if (element) {
element.focus();
}
}
private finishGame(): void {
this.sendScore(this.state.score, this.numTimer, JSON.stringify(this.currentGame));
this.stopTimer();
this.setState({ gamestate: GameState.GameFinished } as IWordGameState);
}
private async sendScore(score: number, seconds: number, details: string): Promise<void> {
if (!this.props.enableHighScores) {
return;
}
await this.wordService.SubmitScore(score, seconds, details);
this.getHighScores();
}
private async getHighScores(): Promise<void> {
if (!this.props.enableHighScores) {
return;
}
const scores: WordGameListItem[] = await this.wordService.GetHighScores();
this.setState({
highScores: scores
} as IWordGameState);
}
private keyDownAnswer(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) {
this.btnAnswer();
}
}
private async btnStartGame(): Promise<void> {
this.setState({
gamestate: GameState.GameLoading
} as IWordGameState);
this.currentGame = this.wordService.GenerateGame();
await new Promise<void>(resolve => { setTimeout(resolve, 1000); });
this.setState({
gamestate: GameState.GamePlaying,
currentRound: 0,
txtAnswer: '',
score: 0,
lblMessage: '',
currentWord: this.currentGame.rounds[0].word,
possibleAnswersText: this.getPossibleAnswersText(0)
} as IWordGameState);
this.numTimer = 30;
this.setState({ lblTimer: this.numTimer + ' seconds' });
this.timerInterval = setInterval(this.timerTick.bind(this), 1000);
}
private timerTick(): void {
// for debugging
// if (this.numTimer>25)
this.numTimer--;
this.setState({ lblTimer: this.numTimer + ' seconds' });
if (this.numTimer === 0) {
this.finishGame();
}
// toast messages
if (this.messageCounter > 0) {
this.messageCounter--;
if (this.messageCounter === 0) {
this.setState({ lblMessage: '' } as IWordGameState);
}
}
}
private stopTimer(): void {
this.setState({ lblTimer: '' });
clearInterval(this.timerInterval);
}
} }

View File

@ -1,8 +1,8 @@
import * as React from "react"; import * as React from 'react';
import { WordGameListItem } from "./WordService" import { WordGameListItem } from './WordService';
export interface IWordHighScoresProps { export interface IWordHighScoresProps {
scores: WordGameListItem[] scores: WordGameListItem[];
} }
export default class WordHighScores extends React.Component<IWordHighScoresProps, {}> { export default class WordHighScores extends React.Component<IWordHighScoresProps, {}> {
@ -13,13 +13,12 @@ export default class WordHighScores extends React.Component<IWordHighScoresProps
} }
public render(): React.ReactElement<IWordHighScoresProps> { public render(): React.ReactElement<IWordHighScoresProps> {
let rank = 1; let rank: number = 1;
return ( return (
<div> <div>
{ {
this.props.scores.length>0 ? this.props.scores.length > 0 ?
<h3 style={{ textDecoration: 'underline' }}>High Scores</h3> : '' <h3 style={{ textDecoration: 'underline' }}>High Scores</h3> : ''
} }
<table style={{ marginLeft: 'Auto', marginRight: 'Auto' }}> <table style={{ marginLeft: 'Auto', marginRight: 'Auto' }}>
<tbody> <tbody>
@ -38,5 +37,4 @@ export default class WordHighScores extends React.Component<IWordHighScoresProps
</div> </div>
); );
} }
} }

View File

@ -1,53 +1,86 @@
// import * as $ from '../assets/jquery.min'; // import * as $ from '../assets/jquery.min';
import { SPComponentLoader } from '@microsoft/sp-loader';
import WordGame from './WordGame';
import { WebPartContext } from '@microsoft/sp-webpart-base'; import { WebPartContext } from '@microsoft/sp-webpart-base';
import { import {
SPHttpClient, SPHttpClient,
SPHttpClientResponse, SPHttpClientResponse,
HttpClientConfiguration,
ISPHttpClientOptions ISPHttpClientOptions
} from '@microsoft/sp-http'; } from '@microsoft/sp-http';
// tslint:disable-next-line: no-any
const wordjpg: any = require('../assets/wordlist.jpg'); const wordjpg: any = require('../assets/wordlist.jpg');
// tslint:disable-next-line: no-any
const $: any = require('../assets/jquery.min'); const $: any = require('../assets/jquery.min');
// const wordlist: any = require('../assets/wordlist.json');
export class Game export class Game {
{ public rounds: Round[] = [];
rounds:Round[] = [];
} }
export class Round export class Round {
{ public word: string = '';
word = ""; public answers: string[] = [];
answers:string[] = []; public correctAnswer: string = '';
correctAnswer = ""; public incorrectAnswers: string[] = [];
incorrectAnswers:string[] = [];
} }
export class WordGameListItem {
// tslint:disable-next-line: variable-name
public Name: string;
// tslint:disable-next-line: variable-name
public Score: number;
// tslint:disable-next-line: variable-name
public Seconds: number;
// tslint:disable-next-line: variable-name
public Details: string;
constructor(name: string, score: number, seconds: number, details: string) {
this.Name = name;
this.Score = score;
this.Seconds = seconds;
this.Details = details;
}
}
export class WordService { export class WordService {
public allwords: string[] = [];
public words3: string[] = [];
public words4: string[] = [];
public words5: string[] = [];
public words6: string[] = [];
public words7: string[] = [];
public words8: string[] = [];
public context: WebPartContext;
allwords: string[] = []; public GenerateGame(): Game {
words3: string[] = [];
words4: string[] = [];
words5: string[] = [];
words6: string[] = [];
words7: string[] = [];
words8: string[] = [];
context:WebPartContext;
GenerateGame():Game const game: Game = new Game();
{
let game = new Game(); const round1: Round = new Round();
round1.word = this.GetRandomScrambledWord(5);
round1.answers = this.FindPossibleWords(round1.word);
let round1 = new Round(); round1.word = this.GetRandomScrambledWord(5); round1.answers = this.FindPossibleWords(round1.word); const round2: Round = new Round();
let round2 = new Round(); round2.word = this.GetRandomScrambledWord(5); round2.answers = this.FindPossibleWords(round2.word); round2.word = this.GetRandomScrambledWord(5);
let round3 = new Round(); round3.word = this.GetRandomScrambledWord(5); round3.answers = this.FindPossibleWords(round3.word); round2.answers = this.FindPossibleWords(round2.word);
let round4 = new Round(); round4.word = this.GetRandomScrambledWord(6); round4.answers = this.FindPossibleWords(round4.word);
let round5 = new Round(); round5.word = this.GetRandomScrambledWord(6); round5.answers = this.FindPossibleWords(round5.word); const round3: Round = new Round();
let round6 = new Round(); round6.word = this.GetRandomScrambledWord(6); round6.answers = this.FindPossibleWords(round6.word); round3.word = this.GetRandomScrambledWord(5);
round3.answers = this.FindPossibleWords(round3.word);
const round4: Round = new Round();
round4.word = this.GetRandomScrambledWord(6);
round4.answers = this.FindPossibleWords(round4.word);
const round5: Round = new Round();
round5.word = this.GetRandomScrambledWord(6);
round5.answers = this.FindPossibleWords(round5.word);
const round6: Round = new Round();
round6.word = this.GetRandomScrambledWord(6);
round6.answers = this.FindPossibleWords(round6.word);
game.rounds.push(round1); game.rounds.push(round1);
game.rounds.push(round2); game.rounds.push(round2);
@ -59,17 +92,16 @@ export class WordService {
return game; return game;
} }
public async loadWords(): Promise<void> {
async loadWords() { // tslint:disable-next-line: no-string-literal
window['wordService'] = this;
window["wordService"] = this;
/* SP Loader Implementation */ /* SP Loader Implementation */
// console.log(jquery); // console.log(jquery);
// await SPComponentLoader.loadScript('../assets/jquery.min.js', { globalExportsName: "ScriptGlobal" }); // await SPComponentLoader.loadScript('../assets/jquery.min.js', { globalExportsName: "ScriptGlobal" });
// console.log('jquery loaded'); // console.log('jquery loaded');
/* JSON File Implementation /* JSON File Implementation
If you have a custom word list you would like to use If you have a custom word list you would like to use
add it as a JSON file in assets/wordlist.json and add it as a JSON file in assets/wordlist.json and
@ -80,24 +112,25 @@ export class WordService {
// for(let i=0;i<wordlistlength;i++) // for(let i=0;i<wordlistlength;i++)
// this.allwords.push(wordvalues[i]); // this.allwords.push(wordvalues[i]);
/* Text File Implementation /* Text File Implementation
Yields the smallest file download size vs JSON (700k vs 1.3mb) Yields the smallest file download size vs JSON (700k vs 1.3mb)
The word list is a text file stored as wordlist.jpg and The word list is a text file stored as wordlist.jpg and
loaded as text/plain using an overrided mime type */ loaded as text/plain using an overrided mime type */
var responseText = await $.ajax({ const responseText: string = await $.ajax({
url: wordjpg, url: wordjpg,
beforeSend: function (xhr) { beforeSend: (xhr) => {
xhr.overrideMimeType("text/plain; charset=x-user-defined"); xhr.overrideMimeType('text/plain; charset=x-user-defined');
} }
}) as string; }) as string;
this.allwords = responseText.split("\r\n"); this.allwords = responseText.split('\r\n');
this.allwords.forEach(word => { this.allwords.forEach(word => {
if (word.indexOf("-") > -1) if (word.indexOf('-') > -1) {
return; return;
if (word.indexOf("-") > -1) }
if (word.indexOf('-') > -1) {
return; return;
}
switch (word.length) { switch (word.length) {
case 3: case 3:
this.words3.push(word); this.words3.push(word);
@ -126,13 +159,13 @@ export class WordService {
} }
GetWordCount(): number { public GetWordCount(): number {
return this.allwords.length; return this.allwords.length;
} }
GetRandomScrambledWord(level: number) { public GetRandomScrambledWord(level: number): string {
let randomWord = ""; let randomWord: string = '';
let randwordnum = 0; let randwordnum: number = 0;
switch (level) { switch (level) {
case 3: case 3:
randwordnum = Math.floor(Math.random() * Math.floor(this.words3.length)); randwordnum = Math.floor(Math.random() * Math.floor(this.words3.length));
@ -162,109 +195,122 @@ export class WordService {
break; break;
} }
let scrambledWord = this.ScrambleWord(randomWord); const scrambledWord: string = this.ScrambleWord(randomWord);
return scrambledWord; return scrambledWord;
} }
FindPossibleWords(currentWord: string) { public FindPossibleWords(currentWord: string): string[] {
//coati // coati
//taco // taco
//currentWord = "coati"; // currentWord = "coati";
let possibleWords: string[] = []; const possibleWords: string[] = [];
this.allwords.forEach(word => { this.allwords.forEach(word => {
let tempword = word;//taco let tempword: string = word; // taco
for (let i = 0; i < currentWord.length; i++) { for (let i: number = 0; i < currentWord.length; i++) {
let letter = currentWord[i]; const letter: string = currentWord[i];
if (tempword.indexOf(letter) > -1) { if (tempword.indexOf(letter) > -1) {
tempword = tempword.slice(0, tempword.indexOf(letter)) + tempword.slice(tempword.indexOf(letter) + 1); tempword = tempword.slice(0, tempword.indexOf(letter)) + tempword.slice(tempword.indexOf(letter) + 1);
} } else {
else {
tempword = 'n'; tempword = 'n';
break; break;
} }
} }
if (tempword.length == 0) if (tempword.length === 0) {
possibleWords.push(word) possibleWords.push(word);
}
}); });
return possibleWords; return possibleWords;
} }
//replace a character in a string public ScrambleWord(word: string): string {
private replaceCharAt(orig:string, index:number, replacement:string): string { let notScrambled: boolean = true;
return orig.substr(0, index) + replacement + orig.substr(index + replacement.length); let scrambledWord: string = '';
} let count: number = 0;
const originalword: string = word;
ScrambleWord(word: string): string {
let notScrambled = true;
let scrambledWord = "";
let count = 0;
var originalword = word;
while (notScrambled) { while (notScrambled) {
word = originalword; word = originalword;
let chars = ''; let chars: string = '';
for (let i = 0; i < word.length; i++) for (let i: number = 0; i < word.length; i++) {
chars += ' '; chars += ' ';
}
let index = 0; let index: number = 0;
while (word.length > 0) { while (word.length > 0) {
let next = Math.floor(Math.random() * Math.floor(word.length)); // Get a random number between 0 and the length of the word. // Get a random number between 0 and the length of the word.
chars = this.replaceCharAt(chars, index, word[next]); // Take the character from the random position and add to our char array. const next: number = Math.floor(Math.random() * Math.floor(word.length));
word = word.substr(0, next) + word.substr(next + 1); // Remove the character from the word.
// Take the character from the random position and add to our char array.
chars = this.replaceCharAt(chars, index, word[next]);
// Remove the character from the word.
word = word.substr(0, next) + word.substr(next + 1);
++index; ++index;
} }
scrambledWord = chars.slice(0); scrambledWord = chars.slice(0);
count++; count++;
if (originalword!=scrambledWord) if (originalword !== scrambledWord) {
notScrambled = false; notScrambled = false;
}
//just in case there is a problem // just in case there is a problem
if (count == 10) if (count === 10) {
notScrambled = false; notScrambled = false;
} }
}
return scrambledWord; return scrambledWord;
} }
//SHAREPOINT APIS // SHAREPOINT APIS
SetContext(context:WebPartContext){ public SetContext(context: WebPartContext): void {
this.context = context; this.context = context;
} }
public async SubmitScore(score:number,seconds:number,details:string){ public async SubmitScore(score: number, seconds: number, details: string): Promise<void> {
try{ try {
await this.CreateListIfNotExists(); await this.CreateListIfNotExists();
await this.CreateListItem(score,seconds,details); await this.CreateListItem(score, seconds, details);
}catch(error){} } catch (error) {
// do nothing
}
} }
async GetHighScores():Promise<WordGameListItem[]>{ public async GetHighScores(): Promise<WordGameListItem[]> {
var scores:WordGameListItem[] = []; let scores: WordGameListItem[] = [];
try{ try {
let result = await this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + "/_api/web/lists/GetByTitle('WordGameList')/items", SPHttpClient.configurations.v1); const result: SPHttpClientResponse = await this.context.spHttpClient.get(
let json:any = await result.json(); this.context.pageContext.web.absoluteUrl
+ "/_api/web/lists/GetByTitle('WordGameList')/items",
SPHttpClient.configurations.v1);
// tslint:disable-next-line: no-any
const json: any = await result.json();
console.log(json); console.log(json);
json.value.forEach(item => { json.value.forEach(item => {
scores.push(new WordGameListItem(item.Title,item.Score,item.Seconds,item.Details)); scores.push(new WordGameListItem(item.Title,
item.Score,
item.Seconds,
item.Details));
}); });
scores.sort((a,b)=> {return b.Score-a.Score}); scores.sort((a, b) => {
return b.Score - a.Score;
});
//top 10 // top 10
if (scores.length>10) if (scores.length > 10) {
scores = scores.slice(0,10); scores = scores.slice(0, 10);
}
console.log('high scores',scores); console.log('high scores', scores);
}catch(error){ } catch (error) {
console.log('could not find list'); console.log('could not find list');
} }
@ -272,18 +318,28 @@ export class WordService {
} }
async CreateListIfNotExists(){ // replace a character in a string
let result = await this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + '/_api/web/lists', SPHttpClient.configurations.v1); private replaceCharAt(orig: string, index: number, replacement: string): string {
let json:any = await result.json(); return orig.substr(0, index) + replacement + orig.substr(index + replacement.length);
let exists = false; }
private async CreateListIfNotExists(): Promise<void> {
const result: SPHttpClientResponse = await this.context.spHttpClient.get(
this.context.pageContext.web.absoluteUrl
+ '/_api/web/lists',
SPHttpClient.configurations.v1);
// tslint:disable-next-line: no-any
const json: any = await result.json();
let exists: boolean = false;
json.value.forEach(list => { json.value.forEach(list => {
if (list.Title=='WordGameList'){ if (list.Title === 'WordGameList') {
console.log('list found'); console.log('list found');
exists = true; exists = true;
} }
}); });
console.log(json); console.log(json);
if (exists==false){ if (exists === false) {
console.log('Attempting to create list'); console.log('Attempting to create list');
await this.CreateList(); await this.CreateList();
await this.AddListColumnNumber('Score'); await this.AddListColumnNumber('Score');
@ -292,117 +348,111 @@ export class WordService {
} }
} }
async CreateListItem(score:number,seconds:number,details:string){ private async CreateListItem(score: number, seconds: number, details: string): Promise<void> {
var listMetadata = { const listMetadata: {} = {
"__metadata": { '__metadata': {
"type": "SP.Data.WordGameListListItem" 'type': 'SP.Data.WordGameListListItem'
}, },
"Title": this.context.pageContext.user.displayName, 'Title': this.context.pageContext.user.displayName,
"Score": score, 'Score': score,
"Seconds": seconds, 'Seconds': seconds,
"Details": details 'Details': details
}; };
var options: ISPHttpClientOptions = { const options: ISPHttpClientOptions = {
headers: { headers: {
"Accept": "application/json;odata=verbose", 'Accept': 'application/json;odata=verbose',
"Content-Type": "application/json;odata=verbose", 'Content-Type': 'application/json;odata=verbose',
"OData-Version": "" //Really important to specify 'OData-Version': '' // Really important to specify
}, },
body: JSON.stringify(listMetadata) body: JSON.stringify(listMetadata)
}; };
let result = await this.context.spHttpClient.post( const result: SPHttpClientResponse = await this.context.spHttpClient.post(
this.context.pageContext.web.absoluteUrl + "/_api/web/lists/GetByTitle('WordGameList')/items", SPHttpClient.configurations.v1,options); this.context.pageContext.web.absoluteUrl
let json:any = await result.json(); + "/_api/web/lists/GetByTitle('WordGameList')/items",
SPHttpClient.configurations.v1, options);
// tslint:disable-next-line: no-any
const json: any = await result.json();
console.log(json); console.log(json);
} }
async CreateList(){ private async CreateList(): Promise<void> {
var listMetadata = { const listMetadata: {} = {
"__metadata": { '__metadata': {
"type": "SP.List" 'type': 'SP.List'
}, },
"AllowContentTypes": true, 'AllowContentTypes': true,
"BaseTemplate": 100, 'BaseTemplate': 100,
"ContentTypesEnabled": true, 'ContentTypesEnabled': true,
"Description": "Holds high scores for the word game", 'Description': 'Holds high scores for the word game',
"Title": "WordGameList" 'Title': 'WordGameList'
}; };
var options: ISPHttpClientOptions = { const options: ISPHttpClientOptions = {
headers: { headers: {
"Accept": "application/json;odata=verbose", 'Accept': 'application/json;odata=verbose',
"Content-Type": "application/json;odata=verbose", 'Content-Type': 'application/json;odata=verbose',
"OData-Version": "" //Really important to specify 'OData-Version': '' // Really important to specify
}, },
body: JSON.stringify(listMetadata) body: JSON.stringify(listMetadata)
}; };
let result = await this.context.spHttpClient.post( const result: SPHttpClientResponse = await this.context.spHttpClient.post(
this.context.pageContext.web.absoluteUrl + '/_api/web/lists', SPHttpClient.configurations.v1,options); this.context.pageContext.web.absoluteUrl + '/_api/web/lists', SPHttpClient.configurations.v1, options);
let json:any = await result.json(); // tslint:disable-next-line: no-any
const json: any = await result.json();
console.log(json); console.log(json);
} }
async AddListColumnMultiLineText(name:string){ private async AddListColumnMultiLineText(name: string): Promise<void> {
var listMetadata = { const listMetadata: {} = {
'__metadata': {'type':'SP.FieldNumber'}, '__metadata': { 'type': 'SP.FieldNumber' },
'FieldTypeKind': 3, 'FieldTypeKind': 3,
'Title': name, 'Title': name
}; };
var options: ISPHttpClientOptions = { const options: ISPHttpClientOptions = {
headers: { headers: {
"Accept": "application/json;odata=verbose", 'Accept': 'application/json;odata=verbose',
"Content-Type": "application/json;odata=verbose", 'Content-Type': 'application/json;odata=verbose',
"OData-Version": "" //Really important to specify 'OData-Version': '' // Really important to specify
}, },
body: JSON.stringify(listMetadata) body: JSON.stringify(listMetadata)
}; };
let result = await this.context.spHttpClient.post( const result: SPHttpClientResponse = await this.context.spHttpClient.post(
this.context.pageContext.web.absoluteUrl + "/_api/web/lists/getbytitle('WordGameList')/fields", SPHttpClient.configurations.v1,options); this.context.pageContext.web.absoluteUrl
let json:any = await result.json(); + "/_api/web/lists/getbytitle('WordGameList')/fields",
SPHttpClient.configurations.v1, options);
// tslint:disable-next-line: no-any
const json: any = await result.json();
console.log(json); console.log(json);
} }
async AddListColumnNumber(name:string){ private async AddListColumnNumber(name: string): Promise<void> {
var listMetadata = { const listMetadata: {} = {
'__metadata': {'type':'SP.FieldNumber'}, '__metadata': { 'type': 'SP.FieldNumber' },
'FieldTypeKind': 9, 'FieldTypeKind': 9,
'Title': name, 'Title': name,
'MinimumValue': 0, 'MinimumValue': 0,
'MaximumValue': 1000000 'MaximumValue': 1000000
}; };
var options: ISPHttpClientOptions = { const options: ISPHttpClientOptions = {
headers: { headers: {
"Accept": "application/json;odata=verbose", 'Accept': 'application/json;odata=verbose',
"Content-Type": "application/json;odata=verbose", 'Content-Type': 'application/json;odata=verbose',
"OData-Version": "" //Really important to specify 'OData-Version': '' // Really important to specify
}, },
body: JSON.stringify(listMetadata) body: JSON.stringify(listMetadata)
}; };
let result = await this.context.spHttpClient.post( const result: SPHttpClientResponse = await this.context.spHttpClient.post(
this.context.pageContext.web.absoluteUrl + "/_api/web/lists/getbytitle('WordGameList')/fields", SPHttpClient.configurations.v1,options); this.context.pageContext.web.absoluteUrl + "/_api/web/lists/getbytitle('WordGameList')/fields",
let json:any = await result.json(); SPHttpClient.configurations.v1, options);
// tslint:disable-next-line: no-any
const json: any = await result.json();
console.log(json); console.log(json);
} }
}
export class WordGameListItem{
Name:string;
Score:number;
Seconds:number;
Details:string;
constructor(name:string,score:number,seconds:number,details:string){
this.Name = name;
this.Score = score;
this.Seconds = seconds;
this.Details = details;
}
} }