Avatar

saolsen

44 public vals
Joined June 7, 2023

P5 sketch

Easily turn a p5.js sketch into a val. See https://www.val.town/v/saolsen/p5_sketch for an example.

Usage

  • Make a p5 sketch, you can import the p5 types to make it easier.
import type * as p5 from "npm:@types/p5";
  • Export any "global" p5 functions. These are functions like setup and draw that p5 will call.

  • Set the val type to http and default export the result of sketch, passing in import.meta.url.

A full example looks like this.

import type * as p5 from "npm:@types/p5";

export function setup() {
  createCanvas(400, 400);
}

export function draw() {
  if (mouseIsPressed) {
    fill(0);
  } else {
    fill(255);
  }
  ellipse(mouseX, mouseY, 80, 80);
}

import { sketch } from "https://esm.town/v/saolsen/p5";
export default sketch(import.meta.url);

How it works

The sketch function returns an http handler that sets up a basic page with p5.js added. It then imports your module from the browser and wires up all the exports so p5.js can see them. All the code in your val will run in the browser (except for the default sketch export) so you can't call any Deno functions, environment variables, or other server side apis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
export function sketch(module: string): (req: Request) => Response {
return function(req: Request): Response {
return new Response(
`
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"
integrity="sha512-N4kV7GkNv7QR7RX9YF/olywyIgIwNvfEe2nZtfyj73HdjCUkAfOBDbcuJ/cTaN04JKRnw1YG1wnUyNKMsNgg3g=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"
integrity="sha512-WzkwpdWEMAY/W8WvP9KS2/VI6zkgejR4/KTxTl4qHx0utqeyVE0JY+S1DlMuxDChC7x0oXtk/ESji6a0lP/Tdg=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<link rel="shortcut icon" href="https://p5js.org/assets/img/favicon.ico" />
<link rel="icon" href="https://p5js.org/assets/img/favicon.ico" />
<style>
* {
padding: 0px;
margin: 0px;
}
</style>
<script type="module">
import * as sketch from "${module}";
for (let f of Object.getOwnPropertyNames(sketch)) {
if (f !== "default") {
window[f] = sketch[f];
}
}
</script>
</head>
<body>
</body>
</html>`,
{ headers: { "Content-Type": "text/html" } },
);
};
}
export default async function(req: Request): Promise<Response> {
return new Response(
`
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"
integrity="sha512-N4kV7GkNv7QR7RX9YF/olywyIgIwNvfEe2nZtfyj73HdjCUkAfOBDbcuJ/cTaN04JKRnw1YG1wnUyNKMsNgg3g=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"
integrity="sha512-WzkwpdWEMAY/W8WvP9KS2/VI6zkgejR4/KTxTl4qHx0utqeyVE0JY+S1DlMuxDChC7x0oXtk/ESji6a0lP/Tdg=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<link rel="shortcut icon" href="https://p5js.org/assets/img/favicon.ico" />
<link rel="icon" href="https://p5js.org/assets/img/favicon.ico" />
<style>
* {
padding: 0px;
margin: 0px;
}
</style>
<script type="module">
import * as sketch from "https://esm.town/v/saolsen/p5_sketch";
for (let f of Object.getOwnPropertyNames(sketch)) {
if (f !== "default") {
window[f] = sketch[f];
}
}
</script>
</head>
<body>
</body>
</html>`,
{ headers: { "Content-Type": "text/html" } },
);
}
1
2
3
4
5
6
7
8
9
10
11
12
import { expect, prettify, Test } from "npm:tiny-jest";
const { it, run, title } = new Test("basic");
it("2+2=4", () => {
expect(2 + 2).toBe(4);
});
it("1+2=4", async () => {
expect(1 + 2).toBe(4);
});
run().then(prettify);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { z } from "npm:zod";
export const COLS = 7;
export const ROWS = 6;
export type Winner = {
kind: "winner";
winner: number;
};
export type Draw = {
kind: "draw";
};
export type Result = Winner | Draw;
export type InProgress = {
status: "in_progress";
next_player: number;
};
export type Over = {
status: "over";
result: Result;
};
export type Status = InProgress | Over;
const stringOrNumber = z.union([z.string(), z.number()]);
export const PlayerI = z.number().nonnegative().lte(1);
export const Slot = z.nullable(PlayerI);
export type Slot = z.infer<typeof Slot>;
export const Column = z.array(Slot).length(6);
export type Column = z.infer<typeof Column>;
export const Board = z.array(Column).length(7);
export type Board = z.infer<typeof Board>;
export const State = z.object({
next_player: PlayerI,
board: Board,
});
export type State = z.infer<typeof State>;
export const Action = z.object({
player: z.coerce.number().nonnegative().lte(1),
column: z.coerce.number().nonnegative().lt(COLS),
});
export type Action = z.infer<typeof Action>;
export function get(state: State, col: number, row: number): Slot {
return state.board[col][row];
}
export function set(state: State, col: number, row: number, slot: Slot): void {
state.board[col][row] = slot;
}
export type ActionCheck =
| "ok"
| "wrong_player"
| "column_full"
| "column_out_of_bounds";
export function check_action(state: State, action: Action): ActionCheck {
if (action.player !== state.next_player) {
return "wrong_player";
}
if (action.column < 0 || action.column >= COLS) {
return "column_out_of_bounds";
}
if (get(state, action.column, ROWS - 1) !== null) {
return "column_full";
}
return "ok";
}
function check_slots_eq(a: Slot, b: Slot, c: Slot, d: Slot): Slot {
if (a === b && b === c && c === d) {
return a;
}
return null;
}
export function status(state: State): Status {
// Check Vertical Win
for (let col = 0; col < COLS; col++) {
for (let row = 0; row < 3; row++) {
let check = check_slots_eq(
get(state, col, row + 0),
get(state, col, row + 1),
get(state, col, row + 2),
get(state, col, row + 3)
);
if (check !== null) {
return { status: "over", result: { kind: "winner", winner: check } };
}
}
}
// Check Horizontal Win
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { agentHandler } from "https://esm.town/v/saolsen/gameplay_agent";
import { GameKind, JsonObject } from "https://esm.town/v/saolsen/gameplay_games";
import {
PokerAction,
PokerActionKind,
PokerAgentResponse,
PokerView,
} from "https://esm.town/v/saolsen/gameplay_poker";
function agent(view: PokerView, _agent_data?: JsonObject): PokerAgentResponse {
const round = view.rounds[view.round];
const player = round.active_player;
const chips = view.player_chips[player];
const amount_to_call = round.bet - round.player_bets[player];
if (round.bet === 0) {
const action: PokerAction = { kind: PokerActionKind.Bet, amount: chips };
return { action };
} else if (amount_to_call >= chips) {
const action: PokerAction = { kind: PokerActionKind.Call };
return { action };
} else {
const raise_amount = chips - amount_to_call;
const action: PokerAction = {
kind: PokerActionKind.Raise,
amount: raise_amount,
};
return { action };
}
}
export default agentHandler(
[
{
game: GameKind.Poker,
agentname: "all_in",
agent: agent,
},
],
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { agentHandler } from "https://esm.town/v/saolsen/gameplay_agent";
import { GameKind, JsonObject } from "https://esm.town/v/saolsen/gameplay_games";
import {
PokerActionKind,
PokerAgentResponse,
PokerView,
} from "https://esm.town/v/saolsen/gameplay_poker";
function fold_action(
view: PokerView,
_agent_data?: JsonObject,
): PokerAgentResponse {
if (view.rounds[view.round].bet === 0) {
return { action: { kind: PokerActionKind.Check } };
}
return { action: { kind: PokerActionKind.Fold } };
}
export default agentHandler(
[
{
game: GameKind.Poker,
agentname: "folds",
agent: fold_action,
},
],
);

gameplay_poker

This is a val.town mirror of gameplay/games/poker.

Click the link to see docs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* # Poker
*
* This module implements a game of poker.
* The poker game is Texas Hold'em, a variant of poker where each player is
* dealt two private cards and shares five community cards.
* This variant is No-Limit 1/2, meaning there is no limit to the amount of
* chips a player can bet, and there are blinds of 1 and 2 chips, which are the
* forced bets for the first two players each round.
*
* Each player gets 100 chips to start the match. The match plays in a series of
* rounds, each round is a hand of poker. The match continues until all players
* except one are out of chips. The remaining player is the winner.
*
* * See {@link Poker} for the {@link Game} definition which links
* to the {@link PokerState} type, the {@link PokerAction} type and the
* update logic {@link checkAction}.
*
* @module
*/
import {
type Game,
GameError,
GameErrorKind,
GameKind,
type Json,
type JsonObject,
type Player,
ResultKind,
type Status,
StatusKind,
Unreachable,
} from "https://esm.town/v/saolsen/gameplay_games";
/**
* The ranks of the cards in a deck.
*/
export enum Rank {
AceLow,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
Ace,
}
/**
* The suits of the playing cards.
*/
export enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
/**
* A playing card.
*/
export interface Card extends JsonObject {
/** The rank of the card. */
rank: Rank;
/** The suit of the card. */
suit: Suit;
}
/**
* The string names for {@link Rank} values.
*/
export const RANK_NAMES = [
"A",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"J",
"Q",
"K",
"A",
];
/**
* The string names for {@link Suit} values.
*/
export const SUIT_NAMES = ["♣", "♦", "♥", "♠"];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { agentHandler } from "https://esm.town/v/saolsen/gameplay_agent";
import {
COLS,
Connect4,
Connect4Action,
Connect4AgentResponse,
Connect4State,
} from "https://esm.town/v/saolsen/gameplay_connect4";
import { GameError, GameKind, ResultKind, StatusKind } from "https://esm.town/v/saolsen/gameplay_games";
const SIMULATIONS = 10000;
function rand_action(state: Connect4State): Connect4Action {
const player = state.active_player;
while (true) {
const column = Math.floor(Math.random() * COLS);
const action: Connect4Action = { column };
if (!(Connect4.checkAction(state, player, action) instanceof GameError)) {
return action;
}
}
}
function score_action(
current_state: Connect4State,
action: Connect4Action,
): number {
const player = current_state.active_player;
// Create a new match with the action applied.
const next_state = JSON.parse(JSON.stringify(current_state));
let status = Connect4.applyAction(next_state, player, action);
// Simulate random games from this state.
let score = 0;
for (let i = 0; i < SIMULATIONS; i++) {
const sim_state = JSON.parse(JSON.stringify(next_state));
// Play out the rest of the game randomly.
if (status instanceof GameError) {
throw status;
}
while (status.status === StatusKind.InProgress) {
const sim_action = rand_action(sim_state);
const next_status = Connect4.applyAction(
sim_state,
sim_state.active_player,
sim_action,
);
if (next_status instanceof GameError) {
throw next_status;
}
status = next_status;
}
if (status.result.kind === ResultKind.Winner) {
if (status.result.players.includes(player)) {
score += 1;
} else {
score -= 1;
}
}
}
return score / SIMULATIONS;
}
function agent(state: Connect4State): Connect4AgentResponse {
// For each action we could take, simulate multiple random games from the resulting state.
// Keep track of the number of wins for each action.
// Pick the action with the highest win rate.
let max_score = Number.MIN_VALUE;
let best_action: Connect4Action = { column: 0 };
for (let col = 0; col < COLS; col++) {
const action: Connect4Action = { column: col };
const check = Connect4.checkAction(state, state.active_player, action);
if (!(check instanceof GameError)) {
const score = score_action(state, action);
if (score > max_score) {
max_score = score;
best_action = action;
}
}
}
return { action: best_action };
}
export default agentHandler(
[
{
game: GameKind.Connect4,
agentname: "mcts",
agent: agent,
},
],
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { GameError, GameKind, JsonObject } from "https://esm.town/v/saolsen/gameplay_games";
import {
COLS,
Connect4,
Connect4Action,
Connect4AgentResponse,
Connect4State,
} from "https://esm.town/v/saolsen/gameplay_connect4";
import { agentHandler } from "https://esm.town/v/saolsen/gameplay_agent";
function rand_action(
state: Connect4State,
agent_data?: { counter: number },
): Connect4AgentResponse {
const counter = agent_data?.counter || 0;
const player = state.active_player;
while (true) {
const column = Math.floor(Math.random() * COLS);
const action: Connect4Action = { column };
if (!(Connect4.checkAction(state, player, action) instanceof GameError)) {
return { action, agent_data: { counter: counter + 1 } };
}
}
}
export default agentHandler(
[
{
game: GameKind.Connect4,
agentname: "rand",
agent: rand_action,
},
],
);

gameplay_connect4

This is a val.town mirror of gameplay/games/connect4.

Click the link to see docs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* # Connect4
*
* This module implements the game Connect4.
*
* Two players take turns dropping pieces into a 7x6 grid.
* The chips fall down to the lowest empty row in the column.
* The first player to get 4 of their pieces in a row (horizontally, vertically,
* or diagonally) wins.
*
* See {@link Connect4} for the {@link Game} definition which links
* to the {@link Connect4State} type, the {@link Connect4Action} type and the
* update logic {@link checkAction}.
*
* @module
*/
import {
type Game,
GameError,
GameErrorKind,
GameKind,
type Json,
type JsonObject,
type Player,
ResultKind,
type Status,
StatusKind,
} from "https://esm.town/v/saolsen/gameplay_games";
/** Number of columns on the board. */
export const COLS = 7;
/** Number of rows on the board. */
export const ROWS = 6;
/** Type for each slot in the board.
* * `null`: Empty slot.
* * `0`: Slot taken by {@link Player} `0` (Blue).
* * `1`: Slot taken by {@link Player} `1` (Red).
*/
export type Slot = null | 0 | 1;
/**
* State of a Connect4 game.
*/
export interface Connect4State extends JsonObject {
/** connect4 */
game: typeof GameKind.Connect4;
/** The index of the active {@link Player} (the player who's turn it is). */
active_player: number;
/** The board of the game.
* * The board is a 2D array of {@link Slot}.
* * The first index is the column and the second index is the row.
* See {@link get} and {@link set} for helper functions
* to access the board by column and row.
*/
board: Slot[][];
}
/**
* An action in a Connect4 game.
*/
export interface Connect4Action extends JsonObject {
/** The column to place the piece in.
* * Must be between 0 and 6.
* The piece will be placed in the lowest empty row in the column.
*/
column: number;
}
/**
* The arguments to create a Connect4 game.
*/
export interface Connect4Args {
/** The players of the game.
* * Must have exactly 2 players.
*/
players: Player[];
}
/**
* Function to create a new Connect4 game.
*
* @param {Connect4Args} args The arguments to create the game.
*
* @returns {[Connect4State, Status] | GameError}
* The initial gamestate and the status of the game,
* or an error if the arguments are invalid.
*/
export function newGame(
args: Connect4Args,
): [Connect4State, Status] | GameError {
const { players } = args;
if (players.length !== 2) {
return new GameError(
GameErrorKind.Args,
"Connect4 requires exactly 2 players.",
);
}

gameplay_games

This is a val.town mirror of gameplay/games.

Click the link to see docs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Error helper for exhaustive switch cases.
*
* @example
* ```ts
* type X = "a" | "b";
* const x: X = "a";
* switch (x) {
* case "a": return "A";
* case "b": return "B";
* default: throw new Unreachable(x);
* }
* ```
*
* If you add a new case to X, the switch statement will be
* a type error because you can't assign the new case to the
* `never` type.
*/
export class Unreachable extends Error {
constructor(x: never) {
super(`Unreachable: ${x}`);
}
}
/** JSON serializable base types. See {@link Json}*/
export type JsonLiteral = string | number | boolean | null;
/**
* A type representing a JSON object. See {@link Json}
*/
export interface JsonObject {
/** Index signature */
[key: string]: Json;
}
/** A type representing a JSON serializable value. */
export type Json = JsonLiteral | JsonObject | Json[];
/** Cloneable base types. See {@link Clone}*/
export type CloneLiteral =
| undefined
| null
| boolean
| number
| string
| bigint
| Uint8Array
| Date
| RegExp;
/** A type representing a value that can be cloned with `structuredClone`. */
export type Clone = CloneLiteral | { [key: string]: Clone } | Clone[];
/**
* The different games.
* * Used to tag {@link Game}.
*/
export enum GameKind {
Connect4 = "connect4",
Poker = "poker",
}
/**
* The kind of player of a game.
* * Used to tag {@link Player}.
*/
export enum PlayerKind {
/** A real person (a user). */
User = "user",
/** An agent (a program playing the game). */
Agent = "agent",
}
/**
* Player of a game that is a real person (a user).
*/
export interface UserPlayer {
kind: typeof PlayerKind.User;
/** The username of the player. */
username: string;
}
/**
* Player of a game that is an agent (a program playing the game).
*/
export interface AgentPlayer {
kind: typeof PlayerKind.Agent;
/** The username of the user who created the agent. */
username: string;
/** The name of the agent. */
agentname: string;
}
/** A player in a game
* * {@link UserPlayer}: A real person.
* * {@link AgentPlayer}: An agent (a program playing the game).
*/
export type Player = UserPlayer | AgentPlayer;
/**