Skip to content
Snippets Groups Projects
Commit 0fa0461b authored by Anthony REY's avatar Anthony REY
Browse files

Mastermind

parent 54ef0b49
No related branches found
No related tags found
1 merge request!77Resolve "Mastermind"
......@@ -32,3 +32,4 @@ include:
- local: "/tennis/refactoring/.gitlab-ci.yml"
- local: "/bowling-game/.gitlab-ci.yml"
- local: "/tennis/greenfield-2/.gitlab-ci.yml"
- local: "/mastermind/.gitlab-ci.yml"
......@@ -59,6 +59,7 @@ Un kata de code est un petit exercice pensé pour s'entrainer jusqu'à maitriser
- [Trip service (TypeScript)](/trip-service-kata-typescript)
- [Roman calculator](/roman-calculator)
- [H2G2](/h2g2)
- [Mastermind](/mastermind)
## Front
......
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
],
plugins: ['prettier', '@typescript-eslint'],
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'never',
},
],
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'always',
},
],
'prettier/prettier': [
'error',
{
singleQuote: true,
trailingComma: 'es5',
printWidth: 140,
},
],
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
},
'import/core-modules': ['sinon'],
},
parserOptions: {
parser: '@typescript-eslint/parser',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-unused-vars': ['off'],
'no-undef': ['off'],
},
},
],
};
/.idea
mastermind:
variables:
PROJECT_FOLDER: "mastermind"
extends: .node14
only:
refs:
- master
- merge_requests
changes:
- ".gitlab-common-ci.yml"
- "mastermind/**/*"
# Mastermind
Résolution en TDD et avec explications du kata [Mastermind](https://codingdojo.org/kata/Mastermind/).
- **Auteurs** : Julie CONTE et Anthony REY
- **Date** : 14/04/2021
- **Langage** : TypeScript
- **Niveau** : Moyen
module.exports = {
roots: ['<rootDir>/src'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testEnvironment: 'node',
};
This diff is collapsed.
{
"name": "mastermind",
"version": "1.0.0",
"description": "Mastermind kata",
"main": "src/index.ts",
"dependencies": {
"@types/jest": "^26.0.22",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"eslint": "^7.23.0",
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.3.1",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"ts-jest": "^26.5.4",
"typescript": "^4.2.4"
},
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:watch:all": "jest --watchAll",
"eslint:ci": "eslint './**/*.{ts,js}'",
"eslint": "eslint './**/*.{ts,js}' --fix"
},
"author": "Anthony REY",
"license": "MIT"
}
type Color = 'purple' | 'pink' | 'red' | 'blue' | 'yellow' | 'green' | 'white' | 'brown';
type Colors = [Color, Color, Color, Color];
type State = 'playing' | 'lost' | 'win';
interface Report {
wellPlaced: number;
misplaced: number;
}
const difference = (colors: Color[], substracts: Color[]) => {
const remaining = [...substracts];
return colors.filter((current) => {
if (remaining.includes(current)) {
const toRemove = remaining.indexOf(current);
remaining.splice(toRemove, 1);
return false;
}
return true;
});
};
const intersect = (secret: Color[], guess: Color[]) => {
const remaining = [...guess];
return secret.filter((current) => {
if (remaining.includes(current)) {
const toRemove = remaining.indexOf(current);
remaining.splice(toRemove, 1);
return true;
}
return false;
});
};
class Mastermind {
private currentState: State = 'playing';
private remaining = 12;
constructor(private secret: Colors) {}
evaluate(guess: Colors): Report {
this.remaining--;
const wellPlaced = this.listWellPlaced(guess);
const rest = difference(this.secret, wellPlaced);
const guessRest = difference(guess, wellPlaced);
const misplaced = intersect(rest, guessRest).length;
const wellPlacedCount = this.countWellPlaced(guess);
this.currentState = this.evaluateState(wellPlacedCount);
return {
wellPlaced: wellPlacedCount,
misplaced,
};
}
private evaluateState(wellPlacedCount: number): State {
if (this.remaining <= 0) {
return 'lost';
}
if (wellPlacedCount === 4) {
return 'win';
}
return 'playing';
}
private listWellPlaced(guess: Color[]): Color[] {
return this.secret.filter((current, index) => current === guess[index]);
}
private countWellPlaced(guess: Color[]): number {
return this.listWellPlaced(guess).length;
}
get state(): State {
return this.currentState;
}
}
const defaultMastermind = () => new Mastermind(['purple', 'pink', 'red', 'blue']);
const evaluateToLose = (mastermind: Mastermind) => {
for (let i = 0; i < 12; i++) {
mastermind.evaluate(['red', 'red', 'red', 'red']);
}
};
describe('Mastermind', () => {
describe('Evaluation', () => {
it('Should have all color well placed', () => {
const mastermind = defaultMastermind();
expect(mastermind.evaluate(['purple', 'pink', 'red', 'blue'])).toEqual<Report>({
wellPlaced: 4,
misplaced: 0,
});
});
it('Should not have any well placed', () => {
const mastermind = defaultMastermind();
expect(mastermind.evaluate(['yellow', 'green', 'white', 'brown'])).toEqual<Report>({
wellPlaced: 0,
misplaced: 0,
});
});
it('Should have only misplaced', () => {
expect(defaultMastermind().evaluate(['pink', 'purple', 'blue', 'red'])).toEqual<Report>({
wellPlaced: 0,
misplaced: 4,
});
});
it('Should have two colors misplaced and two colors well placed', () => {
const mastermind = new Mastermind(['purple', 'pink', 'purple', 'yellow']);
expect(mastermind.evaluate(['purple', 'pink', 'yellow', 'purple'])).toEqual<Report>({
wellPlaced: 2,
misplaced: 2,
});
});
it('Should not misplace repeated secret', () => {
const mastermind = new Mastermind(['red', 'red', 'red', 'red']);
expect(mastermind.evaluate(['red', 'yellow', 'yellow', 'yellow'])).toEqual<Report>({
wellPlaced: 1,
misplaced: 0,
});
});
});
describe('State', () => {
it('Should be pending by default', () => {
expect(defaultMastermind().state).toBe<State>('playing');
});
it('Should lost after 12 tries', () => {
const mastermind = defaultMastermind();
for (let i = 0; i < 12; i++) {
mastermind.evaluate(['red', 'red', 'red', 'red']);
}
expect(mastermind.state).toBe<State>('lost');
});
it('Should win when already well placed', () => {
const mastermind = defaultMastermind();
mastermind.evaluate(['purple', 'pink', 'red', 'blue']);
expect(mastermind.state).toBe<State>('win');
});
it("Can't win when already lost", () => {
const mastermind = defaultMastermind();
evaluateToLose(mastermind);
mastermind.evaluate(['purple', 'pink', 'red', 'blue']);
expect(mastermind.state).toBe<State>('lost');
});
});
describe('Difference', () => {
it('Should not remove duplicate items', () => {
expect(difference(['purple', 'purple'], ['purple', 'pink'])).toEqual(['purple']);
});
});
});
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "dist/main.js",
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment