Skip to content

Commit

Permalink
Bump to v0.7
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyLeland committed Sep 6, 2023
1 parent 01ddb2c commit bff0e2a
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 130 deletions.
160 changes: 116 additions & 44 deletions games/Pente/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<title>Pente v0.5</title>
<title>Pente v0.7</title>
<link rel="stylesheet" href="./base.css">

<style>
Expand Down Expand Up @@ -29,7 +29,13 @@

#ui {
display: flex;
gap: 3vmin;
gap: 1vmin;
}

#players {
display: grid;
grid-template-columns: auto auto;
grid-column-gap: 1vmin;
}

.playerName {
Expand All @@ -42,67 +48,133 @@
<body>
<div id="wrapper">
<!-- <h1>Pente</h1> -->
<canvas id="canvas"></canvas>
<div id="ui">
<div id="players"></div>
<select id="new">
<option selected disabled hidden>New</option>
<option>2</option>
<option>3</option>
<option>4</option>
</select>
<button id="undo">Undo</button>
</div>
<canvas id="canvas"></canvas>
</div>
</body>

<script type="module">
import { PenteCanvas } from './src/PenteCanvas.js';
import { Piece } from './src/Piece.js';

const ui = document.getElementById( 'ui' );
const pente = new PenteCanvas( document.getElementById( 'canvas' ) );
pente.onUIUpdate = () => {
for ( let team = 1; team <= pente.board.teams; team ++ ) {
captureUI[ team - 1 ].innerText = pente.board.captures[ team - 1 ];
playerDiv[ team - 1 ].style.backgroundColor = pente.board.currentTeam == team ?
Piece.PlayerColors[ pente.board.color[ pente.board.currentTeam - 1 ] ] : 'black';
}
};

const playerDiv = [];
const captureUI = [];


[ 1, 2 ].forEach( team => {
const div = document.createElement( 'div' );
let playerDiv;
let captureUI;

const playerName = document.createElement( 'span' );
playerName.className = 'playerName';
playerName.innerText = `Player ${ team }`;
createUI( pente.board );


const span = document.createElement( 'span' );
span.id = `capture${ team }`;
span.textContent = 0;

const label = document.createElement( 'label' );
label.textContent = 'Captures: ';
label.htmlFor = span.id;
// const playersLabel = document.createElement( 'label' );
// playersLabel.innerText= 'Players: ';
// playersLabel.htmlFor = 'numPlayers';

playerDiv.push( div );
captureUI.push( span );
// const playersDropdown = document.createElement( 'select' );
// playersDropdown.id = 'numPlayers';

div.appendChild( playerName );
div.appendChild( label );
div.appendChild( span );
// // TODO: Make this part of New (since it requires a new game anyway)
// [ 2, 3, 4 ].forEach( ( number ) => {
// const option = document.createElement( 'option' );
// option.value = number;
// option.innerText = number;
// playersDropdown.appendChild( option );
// } );

ui.appendChild( div );
// ui.appendChild( playersLabel );
// ui.appendChild( playersDropdown );

return span;
document.getElementById( 'new' ).addEventListener( 'change', e => {
pente.newGame( parseInt( e.target.value ) );
e.target.selectedIndex = 0;
createUI( pente.board );
} );

const newButton = document.createElement( 'button' );
newButton.innerText = 'New';
newButton.addEventListener( 'click', _ => pente.newGame() );
ui.appendChild( newButton );

const undoButton = document.createElement( 'button' );
undoButton.innerText = 'Undo';
undoButton.addEventListener( 'click', _ => pente.undo() );
ui.appendChild( undoButton );

function onUIUpdate( board ) {
board.captures.forEach( ( number, index ) => {
captureUI[ index ].innerText = number;

playerDiv[ index ].style.backgroundColor = board.currentTeam - 1 == index ? Piece.TeamColor[ index ] : 'black';
} );
document.getElementById( 'undo' ).addEventListener( 'click', _ => pente.undo() );

function createUI( board ) {
const playersUI = document.getElementById( 'players' );
playersUI.replaceChildren();

playerDiv = [];
captureUI = [];

for ( let team = 1; team <= pente.board.teams; team ++ ) {
const div = document.createElement( 'div' );

const playerName = document.createElement( 'span' );
playerName.className = 'playerName';
playerName.innerText = `Player ${ team }`;

const colorDropdown = document.createElement( 'select' );

for ( const colorName in Piece.PlayerColors ) {
const option = document.createElement( 'option' );
option.value = colorName;
option.innerText = colorName;
if ( option.value == board.color[ team - 1 ] ) {
option.selected = true;
}
colorDropdown.appendChild( option );
}

colorDropdown.addEventListener( 'change', ( e ) => {
pente.setPlayerColor( team, e.currentTarget.value );
} );

const aiDropdown = document.createElement( 'select' );

[ 'Human', 'AI' ].forEach( ( label, index ) => {
const option = document.createElement( 'option' );
option.value = index;
option.innerText = label;
if ( option.value == board.ai[ team - 1 ] ) {
option.selected = true;
}
aiDropdown.appendChild( option );
} );

aiDropdown.addEventListener( 'change', ( e ) => {
pente.setPlayerAI( team, parseInt( e.currentTarget.value ) );
} );

const span = document.createElement( 'span' );
span.id = `capture${ team }`;
span.textContent = 0;

const label = document.createElement( 'label' );
label.textContent = 'Captures: ';
label.htmlFor = span.id;

playerDiv.push( div );
captureUI.push( span );

div.appendChild( playerName );
div.appendChild( colorDropdown );
div.appendChild( aiDropdown );
div.appendChild( label );
div.appendChild( span );

playersUI.appendChild( div );
}
}

const pente = new PenteCanvas( document.getElementById( 'canvas' ), onUIUpdate );


const wrapper = document.getElementById( 'wrapper' );
window.onresize = ( e ) => {
Expand Down
121 changes: 104 additions & 17 deletions games/Pente/src/Board.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,15 @@ export class Board {
);

history = [];

teams = 2;

currentTeam = 1;


teams = 2;
color = [ 'Red', 'Yellow' ];
ai = Array( this.teams ).fill( 0 );
captures = Array( this.teams ).fill( 0 );


victory = 0;

#pieces = Array.from(
Expand All @@ -99,6 +103,49 @@ export class Board {
}
}

getBestMove() {
let bestScore = 0;
let bestMoves = [];

for ( let row = 0; row <= BoardSize; row ++ ) {
for ( let col = 0; col <= BoardSize; col ++ ) {
const move = this.getMove( col, row );
if ( move ) {

// For now, score by how long a series this gives us,
// as well as how long an enemy series it prevents
// TODO: Weight our gain differently than others potential gains?
let score = 0;
for ( let i = 1; i <= this.teams; i ++ ) {
const weight = i == this.currentTeam ? 1.5 : 1;
score += Math.pow( this.getLongest( col, row, i ), 2 ) * weight;
}

// TODO: How to weight captures compared to adds?
move.captures?.forEach( captured => {
score += this.getLongest( captured.col, captured.row, captured.team );
} );

if ( score > bestScore ) {
bestScore = score;
bestMoves = [ move ];
}
else if ( score == bestScore ) {
bestMoves.push( move );
}
}
}
}

console.log( 'best score = ' + bestScore );

return bestMoves[ Math.floor( Math.random() * bestMoves.length ) ];
}

getScore( team ) {
// TODO: number of captures it provides, longest
}

getMove( col, row ) {
if ( this.getTeam( col, row ) == 0 ) {
const move = {
Expand Down Expand Up @@ -130,23 +177,46 @@ export class Board {
}
}

applyMove( move ) {
// In a row
this.board[ move.col ][ move.row ] = move.team;

// How long a series would we make by going here?
getLongest( col, row, team ) {
let longest = 0;
[ [ -1, -1 ], [ 0, -1 ], [ 1, -1 ], [ -1, 0 ] ].forEach( dir => {
let numSame = -1; // since we'll be counting col,row twice

let numSame = 1; // assume team placed at col,row
let numEmpty = 0;

[ -1, 1 ].forEach( posneg => {
for ( let col = move.col, row = move.row;
this.getTeam( col, row ) == move.team;
col += dir[ 0 ] * posneg, row += dir[ 1 ] * posneg, numSame ++ );
const dCol = dir[ 0 ] * posneg;
const dRow = dir[ 1 ] * posneg;

let c = col + dCol;
let r = row + dRow;

for ( ;
this.getTeam( c, r ) == team;
c += dCol, r += dRow, numSame ++ );

for ( ;
this.getTeam( c, r ) == 0;
c += dCol, r += dRow, numEmpty ++ );
} );

longest = Math.max( longest, numSame );

// Since we only care about length in terms of victory, we can help
// the AI by ignoring lengths when there aren't enough empties to
// make it up to 5
if ( numSame + numEmpty >= 5 ) {
longest = Math.max( longest, numSame );
}
} );

return longest;
}

applyMove( move ) {
// In a row
this.board[ move.col ][ move.row ] = move.team;

const longest = this.getLongest( move.col, move.row, move.team );

console.log( 'longest made = ' + longest );

if ( longest >= 5 ) {
Expand All @@ -170,13 +240,16 @@ export class Board {
if ( this.victory ) {
this.#victoryText = `Player ${ move.team } Wins!`;
}

this.currentTeam = this.currentTeam % this.teams + 1;
else {
this.currentTeam = this.currentTeam % this.teams + 1;
}

this.history.push( move );
}

undo() {
// TODO: Undo to last human move? (Otherwise, AI moves will immediately redo)

const move = this.history.pop();

if ( move ) {
Expand All @@ -196,6 +269,8 @@ export class Board {
}
}

onUIUpdate() {}
onReady() {}

update( dt ) {
let changed = false;
Expand All @@ -207,7 +282,7 @@ export class Board {
const piece = this.#pieces[ col ][ row ];

if ( team > 0 ) {
piece.team = team;
piece.colorKey = this.color[ team - 1 ];
changed |= piece.grow( dt );
}
}
Expand All @@ -227,6 +302,18 @@ export class Board {
}
}

if ( !changed ) {
this.onUIUpdate();

if ( !this.victory && this.ai[ this.currentTeam - 1 ] ) {
this.applyMove( this.getBestMove() );
return true;
}
else {
this.onReady();
}
}

return changed;
}

Expand Down
Loading

0 comments on commit bff0e2a

Please sign in to comment.