Compare commits
43 Commits
a2661f4369
...
cat-town
Author | SHA1 | Date | |
---|---|---|---|
09ee33a751
|
|||
ee9fb1955c
|
|||
41f53c34c0
|
|||
6c0083e094
|
|||
7f2f579241
|
|||
7d2ac20ce1
|
|||
8f38a3b271
|
|||
96f5f85a27
|
|||
6cf80e09b1
|
|||
30ed115ed0
|
|||
8376608a1e | |||
3de96b813f
|
|||
a73acedc6d
|
|||
92e3d6fe5b
|
|||
395006cdb8
|
|||
622d47071a
|
|||
1452d544bb
|
|||
56c6a8cf09
|
|||
cc29386cee
|
|||
95453bf197
|
|||
a6526af6ff
|
|||
3ea30a997a
|
|||
03edacc8ec
|
|||
d1ffbe25a3
|
|||
dc3a40b19d
|
|||
f2e0de1b66
|
|||
58842504c4
|
|||
1080737931
|
|||
de6e907c60
|
|||
9989801e62
|
|||
8c280c303e
|
|||
b2e61cd0d2
|
|||
c15bafe55d
|
|||
af7d5c6e14
|
|||
512eddc1ab
|
|||
2bc9788441
|
|||
c52d9b9563
|
|||
fbb04df86d
|
|||
e30cbe2aca
|
|||
116ad347db
|
|||
1493deac96
|
|||
a7e45b8add
|
|||
f1cc614836
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,9 @@
|
|||||||
*.hex
|
*.hex
|
||||||
*.log
|
*.log
|
||||||
|
*.out
|
||||||
*env
|
*env
|
||||||
.dep
|
.dep
|
||||||
**/target/
|
**/target/
|
||||||
/hello_world/main
|
/hello_world/main
|
||||||
/aoc*/**/input
|
/aoc*/**/input
|
||||||
|
/arduino/build-*/
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (C) 2021 Dan Buch
|
Copyright (C) 2022 Dan Buch
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
2
arduino/.envrc
Normal file
2
arduino/.envrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export ARDMK_DIR=/usr/share/arduino
|
||||||
|
export ARDMK_VENDOR=archlinux-arduino
|
3
arduino/Makefile
Normal file
3
arduino/Makefile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
BOARD_TAG = uno
|
||||||
|
|
||||||
|
include $(ARDMK_DIR)/Arduino.mk
|
31
arduino/sos.ino
Normal file
31
arduino/sos.ino
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#define DIT_DURATION_MS 88
|
||||||
|
#define LETTER_PAUSE_MS 1000
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
pinMode(13, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dit() {
|
||||||
|
digitalWrite(13, HIGH);
|
||||||
|
delay(DIT_DURATION_MS);
|
||||||
|
digitalWrite(13, LOW);
|
||||||
|
delay(DIT_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dah() {
|
||||||
|
digitalWrite(13, HIGH);
|
||||||
|
delay(DIT_DURATION_MS * 3);
|
||||||
|
digitalWrite(13, LOW);
|
||||||
|
delay(DIT_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
dit(); dit(); dit();
|
||||||
|
delay(LETTER_PAUSE_MS);
|
||||||
|
|
||||||
|
dah();
|
||||||
|
delay(LETTER_PAUSE_MS);
|
||||||
|
|
||||||
|
dit(); dit(); dit();
|
||||||
|
delay(LETTER_PAUSE_MS);
|
||||||
|
}
|
3
cat-town/.gitignore
vendored
Normal file
3
cat-town/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/node_modules/
|
||||||
|
/src/*.js
|
||||||
|
/dist/
|
4838
cat-town/package-lock.json
generated
Normal file
4838
cat-town/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
cat-town/package.json
Normal file
30
cat-town/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "cat-town",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"webpack": "webpack",
|
||||||
|
"dev": "webpack serve --config webpack.development.js",
|
||||||
|
"start": "npm run dev",
|
||||||
|
"build:dev": "webpack --config webpack.development.js",
|
||||||
|
"build:prod": "webpack --config webpack.production.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@excaliburjs/testing": "^0.25.1",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"compression-webpack-plugin": "^7.1.2",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"source-map-loader": "^2.0.2",
|
||||||
|
"terser-webpack-plugin": "^5.3.6",
|
||||||
|
"ts-loader": "^9.4.2",
|
||||||
|
"typescript": "^4.9.4",
|
||||||
|
"webpack": "^5.75.0",
|
||||||
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-dev-server": "^4.11.1",
|
||||||
|
"webpack-merge": "^5.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"excalibur": "^0.27.0"
|
||||||
|
}
|
||||||
|
}
|
17
cat-town/src/actors/player.ts
Normal file
17
cat-town/src/actors/player.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Actor, Color, vec } from 'excalibur'
|
||||||
|
import { Resources } from '../resources'
|
||||||
|
|
||||||
|
export class Player extends Actor {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
pos: vec(150, 150),
|
||||||
|
width: 25,
|
||||||
|
height: 25,
|
||||||
|
color: new Color(255, 100, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onInitialize() {
|
||||||
|
this.graphics.use(Resources.Cat.toSprite())
|
||||||
|
}
|
||||||
|
}
|
16
cat-town/src/cat.ts
Normal file
16
cat-town/src/cat.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// type Cat = {
|
||||||
|
// name string;
|
||||||
|
// years int;
|
||||||
|
// centimeters int;
|
||||||
|
// kilograms int;
|
||||||
|
// coloring Coloring;
|
||||||
|
// pattern Pattern;
|
||||||
|
// mood Mood;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// type Coloring = 'orange' | 'black' | 'brown' | 'blue';
|
||||||
|
//
|
||||||
|
// type Pattern = 'plain' | 'striped' | 'spotted';
|
||||||
|
//
|
||||||
|
// type Mood = 'happy' | 'sad' | 'purring' |
|
||||||
|
// 'screaming' | 'mad' | 'scratchy' | 'curious' | 'concerned';
|
BIN
cat-town/src/images/cat.png
Normal file
BIN
cat-town/src/images/cat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
BIN
cat-town/src/images/cat.png-autosave.kra
Normal file
BIN
cat-town/src/images/cat.png-autosave.kra
Normal file
Binary file not shown.
30
cat-town/src/index.ts
Normal file
30
cat-town/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Engine, Loader, DisplayMode } from 'excalibur'
|
||||||
|
import { Beginning } from './scenes/beginning'
|
||||||
|
import { Player } from './actors/player'
|
||||||
|
import { Resources } from './resources'
|
||||||
|
|
||||||
|
class Game extends Engine {
|
||||||
|
private player: Player
|
||||||
|
private beginning: Beginning
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({ displayMode: DisplayMode.FitScreen })
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
this.beginning = new Beginning()
|
||||||
|
this.player = new Player()
|
||||||
|
this.beginning.add(this.player)
|
||||||
|
|
||||||
|
game.add('beginning', this.beginning)
|
||||||
|
|
||||||
|
const loader = new Loader(Object.values(Resources))
|
||||||
|
|
||||||
|
return super.start(loader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const game = new Game()
|
||||||
|
game.start().then(() => {
|
||||||
|
game.goToScene('beginning')
|
||||||
|
})
|
8
cat-town/src/resources.ts
Normal file
8
cat-town/src/resources.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { ImageSource } from 'excalibur'
|
||||||
|
import catImage from './images/cat.png'
|
||||||
|
|
||||||
|
const Resources = {
|
||||||
|
Cat: new ImageSource(catImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Resources }
|
7
cat-town/src/scenes/beginning.ts
Normal file
7
cat-town/src/scenes/beginning.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Engine, Scene } from 'excalibur'
|
||||||
|
|
||||||
|
export class Beginning extends Scene {
|
||||||
|
public onInitialize(engine: Engine) {}
|
||||||
|
public onActivate() {}
|
||||||
|
public onDeactivate() {}
|
||||||
|
}
|
22
cat-town/src/town.ts
Normal file
22
cat-town/src/town.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// type Town = {
|
||||||
|
// grid Grid;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// type Grid = {
|
||||||
|
// squares Map<Coords, Square>;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// type Coords = {
|
||||||
|
// x int;
|
||||||
|
// y int;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// type Square = {
|
||||||
|
// description string;
|
||||||
|
// things Map<string, Thing>;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// type Thing = {
|
||||||
|
// name string;
|
||||||
|
// description string;
|
||||||
|
// };
|
14
cat-town/tsconfig.json
Normal file
14
cat-town/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "es6",
|
||||||
|
"types": ["excalibur"],
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
47
cat-town/webpack.common.js
Normal file
47
cat-town/webpack.common.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||||
|
const HtmlWebPackPlugin = require("html-webpack-plugin");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./src/index.ts",
|
||||||
|
target: "web",
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
sourceMapFilename: "[file].map",
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
},
|
||||||
|
devtool: "source-map",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||||
|
type: "asset/resource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
use: ["source-map-loader"],
|
||||||
|
exclude: [path.resolve(__dirname, "node_modules/excalibur")],
|
||||||
|
enforce: "pre",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: "ts-loader",
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: [".tsx", ".ts", ".js"],
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
chunks: "all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new CleanWebpackPlugin(),
|
||||||
|
new HtmlWebPackPlugin({
|
||||||
|
title: "Cat Town",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
12
cat-town/webpack.development.js
Normal file
12
cat-town/webpack.development.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const { merge } = require("webpack-merge");
|
||||||
|
const common = require("./webpack.common");
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: "development",
|
||||||
|
devtool: "inline-source-map",
|
||||||
|
devServer: {
|
||||||
|
static: {
|
||||||
|
directory: "./dist",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
13
cat-town/webpack.production.js
Normal file
13
cat-town/webpack.production.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const { merge } = require("webpack-merge");
|
||||||
|
const CompressionWebpackPlugin = require("compression-webpack-plugin");
|
||||||
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
|
const common = require("./webpack.common");
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: "production",
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
minimizer: [new TerserPlugin()],
|
||||||
|
},
|
||||||
|
plugins: [new CompressionWebpackPlugin()],
|
||||||
|
});
|
207
hyrule/README.md
Normal file
207
hyrule/README.md
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
# Hyrule
|
||||||
|
|
||||||
|
A card game for 2-7 players.
|
||||||
|
|
||||||
|
## requirements
|
||||||
|
|
||||||
|
Use a standard card playing deck, keeping Jokers. Shuffle however
|
||||||
|
you like.
|
||||||
|
|
||||||
|
## dealing
|
||||||
|
|
||||||
|
Each player is dealt 7 cards, kept secret from other players.
|
||||||
|
|
||||||
|
## turns
|
||||||
|
|
||||||
|
There are two variations for taking turns. "Chance rules" is
|
||||||
|
strictly a matter of chance, and is recommended for impatient
|
||||||
|
players because it tends to be faster-paced and has more of a
|
||||||
|
*surprise!* element. "Strategy rules" introduces an element of
|
||||||
|
strategy and is better suited for players with more _patience_.
|
||||||
|
|
||||||
|
### chance rules
|
||||||
|
|
||||||
|
Each player selects one card and places it face down in the middle
|
||||||
|
playing area between themselves and other players.
|
||||||
|
|
||||||
|
On the count of 3, all said together in rhythm, each player flips
|
||||||
|
over their own card. Precedence applies and the winning player
|
||||||
|
takes all cards.
|
||||||
|
|
||||||
|
### strategy rules
|
||||||
|
|
||||||
|
On the first turn or when an equal number of cards have been
|
||||||
|
captured by each player, the tallest player goes first. On each
|
||||||
|
subsequent turn, the player with the most captured cards plays
|
||||||
|
first. The play order then proceeds counter-clockwise.
|
||||||
|
|
||||||
|
When taking one's turn, the player may choose to either *play* or
|
||||||
|
*swap*.
|
||||||
|
|
||||||
|
#### strategy *play*
|
||||||
|
|
||||||
|
When choosing to *play*, the player selects a card and places it
|
||||||
|
face-up in the center play area. The remaining players take turns
|
||||||
|
counter-clockwise, each placing a card face-up in the center play
|
||||||
|
area. Precedence rules apply and the winning player takes all
|
||||||
|
cards.
|
||||||
|
|
||||||
|
#### strategy *swap*
|
||||||
|
|
||||||
|
When choosing to *swap*, the player discards one card face-up in
|
||||||
|
the discard pile (next to the stock pile) and then draws a card
|
||||||
|
from the stock, which should be laying face-down. The goal here may
|
||||||
|
be to take a chance at getting a better card than one is
|
||||||
|
discarding, or to force the next player in the rotation to play, or
|
||||||
|
both.
|
||||||
|
|
||||||
|
> **NOTE**: a player may only choose to *swap* if the player on the
|
||||||
|
> previous turn _did not swap_.
|
||||||
|
|
||||||
|
## precedence
|
||||||
|
|
||||||
|
The following rules apply across suits with the exception of
|
||||||
|
*jokers and fives* (explained below). Cards within a single suit
|
||||||
|
are compared with higher-value cards winning. Cards are counted
|
||||||
|
from ace (1) through king (13).
|
||||||
|
|
||||||
|
Ways to think about these rules could include:
|
||||||
|
|
||||||
|
- "rupee buys bomb" / money buys weapon
|
||||||
|
- "rupee buys sword" / money buys weapon
|
||||||
|
- "bomb blows up sword" / range weapon beats melee weapon
|
||||||
|
- "bomb blows up heart" / range weapon beats unarmed
|
||||||
|
- "sword cuts through heart" / melee weapon beats unarmed
|
||||||
|
- "heart is stronger than rupee" / love conquers money
|
||||||
|
|
||||||
|
**NOTE:** In the case of a turn that involves 3 or more cards, the
|
||||||
|
presence of both diamonds *and* hearts will result in the
|
||||||
|
highest-value heart winning the turn.
|
||||||
|
|
||||||
|
### diamonds ("rupees")
|
||||||
|
|
||||||
|
Beats:
|
||||||
|
|
||||||
|
- clubs ("bombs")
|
||||||
|
- spades ("swords")
|
||||||
|
|
||||||
|
### clubs ("bombs")
|
||||||
|
|
||||||
|
Beats:
|
||||||
|
|
||||||
|
- spades ("swords")
|
||||||
|
- hearts
|
||||||
|
|
||||||
|
### spades ("swords")
|
||||||
|
|
||||||
|
Beats:
|
||||||
|
|
||||||
|
- hearts
|
||||||
|
|
||||||
|
### hearts
|
||||||
|
|
||||||
|
Beats:
|
||||||
|
|
||||||
|
- diamonds ("rupees")
|
||||||
|
|
||||||
|
### jokers ("tingles")
|
||||||
|
|
||||||
|
Beats everything except *fives*
|
||||||
|
|
||||||
|
### fives
|
||||||
|
|
||||||
|
The 5 of a given suit will be granted special status *only* when
|
||||||
|
played against a joker ("tingle") and will win with the following
|
||||||
|
sub-precedence, which is roughly the inverse of the main
|
||||||
|
precedence:
|
||||||
|
|
||||||
|
- 5 of hearts
|
||||||
|
- 5 of spades / "master sword"
|
||||||
|
- 5 of clubs / "bomb cluster"
|
||||||
|
- 5 of diamonds / "blue rupee"
|
||||||
|
|
||||||
|
## scoring
|
||||||
|
|
||||||
|
At the conclusion of a round of 7 cards, the player with the most
|
||||||
|
cards is the winner. The face values of the cards are not
|
||||||
|
considered at scoring time. A draw may be handled in a "run-off
|
||||||
|
game" or ignored as you like.
|
||||||
|
|
||||||
|
## examples
|
||||||
|
|
||||||
|
In the following examples, the **winning card** is highlighted at
|
||||||
|
the top of each list, followed by an explanation for the outcome.
|
||||||
|
|
||||||
|
### 2-player
|
||||||
|
|
||||||
|
- **2 of clubs**
|
||||||
|
- 2 of spades
|
||||||
|
|
||||||
|
Clubs are higher-value than spades, or "bomb beats sword".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **2 of clubs**
|
||||||
|
- ace of clubs
|
||||||
|
|
||||||
|
Cards within the same suit are compared at face value with aces
|
||||||
|
being *1*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **ace of hearts**
|
||||||
|
- king of diamonds
|
||||||
|
|
||||||
|
Hearts take precedence over diamonds, or "love is stronger than
|
||||||
|
money".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **joker**
|
||||||
|
- king of hearts
|
||||||
|
|
||||||
|
The opposing card is not a *five*, or "tingle takes _(thing)_".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **5 of spades**
|
||||||
|
- joker
|
||||||
|
|
||||||
|
The 5 of any suit will beat a joker given its special item status
|
||||||
|
in that scenario, or "tingle is distracted by the beauty of
|
||||||
|
_(thing)_".
|
||||||
|
|
||||||
|
### 3-player
|
||||||
|
|
||||||
|
- **2 of diamonds**
|
||||||
|
- 2 of clubs
|
||||||
|
- 2 of spades
|
||||||
|
|
||||||
|
Diamonds have higher precedence than clubs and spades, and there
|
||||||
|
are no hearts present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **6 of clubs**
|
||||||
|
- 6 of spades
|
||||||
|
- 6 of hearts
|
||||||
|
|
||||||
|
Clubs have higher precedence than spades and hearts, and there are
|
||||||
|
no diamonds present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **8 of hearts**
|
||||||
|
- 8 of diamonds
|
||||||
|
- 8 of spades
|
||||||
|
|
||||||
|
When both diamonds and hearts are present, hearts is highest
|
||||||
|
precedence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **king of diamonds**
|
||||||
|
- jack of diamonds
|
||||||
|
- queen of diamonds
|
||||||
|
|
||||||
|
Within the same suit, cards are compared by face value.
|
BIN
hyrule/README.pdf
Normal file
BIN
hyrule/README.pdf
Normal file
Binary file not shown.
86
hyrule/cards.py
Normal file
86
hyrule/cards.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import dataclasses
|
||||||
|
import enum
|
||||||
|
import random
|
||||||
|
|
||||||
|
Suit = enum.Enum(
|
||||||
|
"Suit",
|
||||||
|
"""
|
||||||
|
CLUBS
|
||||||
|
DIAMONDS
|
||||||
|
HEARTS
|
||||||
|
SPADES
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
FaceValue = enum.Enum(
|
||||||
|
"FaceValue",
|
||||||
|
"""
|
||||||
|
ACE
|
||||||
|
TWO
|
||||||
|
THREE
|
||||||
|
FOUR
|
||||||
|
FIVE
|
||||||
|
SIX
|
||||||
|
SEVEN
|
||||||
|
EIGHT
|
||||||
|
NINE
|
||||||
|
TEN
|
||||||
|
JACK
|
||||||
|
QUEEN
|
||||||
|
KING
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Card:
|
||||||
|
suit: Suit
|
||||||
|
face_value: FaceValue
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.face_value} of {self.suit}"
|
||||||
|
|
||||||
|
|
||||||
|
class Joker:
|
||||||
|
def __str__(self):
|
||||||
|
return "JOKER"
|
||||||
|
|
||||||
|
|
||||||
|
class Deck:
|
||||||
|
def __init__(self, n_jokers=2):
|
||||||
|
self._cards = list(Deck.generate(n_jokers=n_jokers))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Deck len={len(self)}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
as_string = []
|
||||||
|
for card in self._cards:
|
||||||
|
as_string.append(str(card))
|
||||||
|
return "\n".join(as_string)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._cards)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
if len(self._cards) > 0:
|
||||||
|
return self._cards.pop()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
random.shuffle(self._cards)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def cut(self):
|
||||||
|
cut_point = random.randint(0, len(self))
|
||||||
|
self._cards = self._cards[cut_point:] + self._cards[:cut_point]
|
||||||
|
return self
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate(n_jokers=2):
|
||||||
|
for _ in range(n_jokers):
|
||||||
|
yield Joker()
|
||||||
|
|
||||||
|
for suit in Suit:
|
||||||
|
for face_value in FaceValue:
|
||||||
|
yield Card(suit=suit, face_value=face_value)
|
66
hyrule/hyrule.py
Normal file
66
hyrule/hyrule.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import dataclasses
|
||||||
|
import pprint
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import cards
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--player-count", "-c", default=2, type=int, help="The number of players"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--turn-count",
|
||||||
|
"-n",
|
||||||
|
default=5,
|
||||||
|
type=int,
|
||||||
|
help="The number of turns to play",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
Hyrule(player_count=args.player_count, turn_count=args.turn_count).play()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Player:
|
||||||
|
index: int
|
||||||
|
hand: typing.Set[cards.Card]
|
||||||
|
captured: typing.Set[cards.Card]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def score(self):
|
||||||
|
return len(self.captured)
|
||||||
|
|
||||||
|
|
||||||
|
class Hyrule:
|
||||||
|
def __init__(self, player_count=2, turn_count=5):
|
||||||
|
self._players = [
|
||||||
|
Player(index=i, hand=set(), captured=set()) for i in range(player_count)
|
||||||
|
]
|
||||||
|
self._turn_count = turn_count
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
for turn in self._each_turn():
|
||||||
|
self._show_turn(turn)
|
||||||
|
|
||||||
|
def _each_turn(self):
|
||||||
|
for turn_number in range(self._turn_count):
|
||||||
|
yield self._simulate_turn(turn_number)
|
||||||
|
|
||||||
|
def _simulate_turn(self, turn_number):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _show_turn(self, turn):
|
||||||
|
print(turn)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
2
hyrule/justfile
Normal file
2
hyrule/justfile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
build:
|
||||||
|
pandoc -r markdown -w pdf -o README.pdf README.md
|
1876
piston-tutorials/getting-started/Cargo.lock
generated
Normal file
1876
piston-tutorials/getting-started/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
piston-tutorials/getting-started/Cargo.toml
Normal file
17
piston-tutorials/getting-started/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "spinning-square"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
"Dan Buch <dan@meatballhat.com>",
|
||||||
|
"TyOverby <ty@pre-alpha.com>",
|
||||||
|
"Nikita Pekin <contact@nikitapek.in>"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "spinning-square"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
piston = "0.53.0"
|
||||||
|
piston2d-graphics = "0.43.0"
|
||||||
|
pistoncore-glutin_window = "0.70.1"
|
||||||
|
piston2d-opengl_graphics = "0.82.0"
|
70
piston-tutorials/getting-started/src/main.rs
Normal file
70
piston-tutorials/getting-started/src/main.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
extern crate glutin_window;
|
||||||
|
extern crate graphics;
|
||||||
|
extern crate opengl_graphics;
|
||||||
|
extern crate piston;
|
||||||
|
|
||||||
|
use glutin_window::GlutinWindow as Window;
|
||||||
|
use opengl_graphics::{GlGraphics, OpenGL};
|
||||||
|
use piston::event_loop::{EventSettings, Events};
|
||||||
|
use piston::input::{RenderArgs, RenderEvent, UpdateArgs, UpdateEvent};
|
||||||
|
use piston::window::WindowSettings;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
gl: GlGraphics,
|
||||||
|
rotation: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn render(&mut self, args: &RenderArgs) {
|
||||||
|
use graphics::*;
|
||||||
|
|
||||||
|
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
|
||||||
|
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
|
||||||
|
|
||||||
|
let square = rectangle::square(0.0, 0.0, 50.0);
|
||||||
|
let rotation = self.rotation;
|
||||||
|
let (x, y) = (args.window_size[0] / 2.0, args.window_size[1] / 2.0);
|
||||||
|
|
||||||
|
self.gl.draw(args.viewport(), |c, gl| {
|
||||||
|
clear(GREEN, gl);
|
||||||
|
|
||||||
|
let transform = c
|
||||||
|
.transform
|
||||||
|
.trans(x, y)
|
||||||
|
.rot_rad(rotation)
|
||||||
|
.trans(-25.0, -25.0);
|
||||||
|
|
||||||
|
rectangle(RED, square, transform, gl);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, args: &UpdateArgs) {
|
||||||
|
self.rotation += 2.0 * args.dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opengl = OpenGL::V3_2;
|
||||||
|
|
||||||
|
let mut window: Window = WindowSettings::new("spinning-square", [200, 200])
|
||||||
|
.graphics_api(opengl)
|
||||||
|
.exit_on_esc(true)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut app = App {
|
||||||
|
gl: GlGraphics::new(opengl),
|
||||||
|
rotation: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut events = Events::new(EventSettings::new());
|
||||||
|
while let Some(e) = events.next(&mut window) {
|
||||||
|
if let Some(args) = e.render_args() {
|
||||||
|
app.render(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(args) = e.update_args() {
|
||||||
|
app.update(&args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1876
piston-tutorials/sudoku/Cargo.lock
generated
Normal file
1876
piston-tutorials/sudoku/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
piston-tutorials/sudoku/Cargo.toml
Normal file
12
piston-tutorials/sudoku/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "sudoku"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
piston = "0.53.2"
|
||||||
|
piston2d-graphics = "0.43.0"
|
||||||
|
piston2d-opengl_graphics = "0.82.0"
|
||||||
|
pistoncore-glutin_window = "0.70.1"
|
BIN
piston-tutorials/sudoku/assets/FiraSans-Regular.ttf
Normal file
BIN
piston-tutorials/sudoku/assets/FiraSans-Regular.ttf
Normal file
Binary file not shown.
99
piston-tutorials/sudoku/assets/LICENSE
Normal file
99
piston-tutorials/sudoku/assets/LICENSE
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
Copyright (c) 2014, Mozilla Foundation https://mozilla.org/
|
||||||
|
with Reserved Font Name Fira Sans.
|
||||||
|
|
||||||
|
Copyright (c) 2014, Mozilla Foundation https://mozilla.org/
|
||||||
|
with Reserved Font Name Fira Mono.
|
||||||
|
|
||||||
|
Copyright (c) 2014, Telefonica S.A.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
155
piston-tutorials/sudoku/src/gameboard.rs
Normal file
155
piston-tutorials/sudoku/src/gameboard.rs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
//! Game board logic.
|
||||||
|
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
const SIZE: usize = 9;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
pub struct Cell {
|
||||||
|
pub value: u8,
|
||||||
|
pub loaded: bool,
|
||||||
|
pub invalid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Gameboard {
|
||||||
|
pub cells: [[Cell; SIZE]; SIZE],
|
||||||
|
pub completed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gameboard {
|
||||||
|
pub fn new() -> Gameboard {
|
||||||
|
Gameboard {
|
||||||
|
cells: [[Cell::default(); SIZE]; SIZE],
|
||||||
|
completed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_cells(cells: [[u8; SIZE]; SIZE]) -> Gameboard {
|
||||||
|
let mut ret = Gameboard::new();
|
||||||
|
for (i, row) in cells.iter().enumerate() {
|
||||||
|
for (j, &col) in row.iter().enumerate() {
|
||||||
|
ret.cells[i][j] = Cell {
|
||||||
|
value: col,
|
||||||
|
loaded: col != 0,
|
||||||
|
invalid: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn char(&self, ind: [usize; 2]) -> Option<char> {
|
||||||
|
Some(match self.cells[ind[1]][ind[0]].value {
|
||||||
|
1 => '1',
|
||||||
|
2 => '2',
|
||||||
|
3 => '3',
|
||||||
|
4 => '4',
|
||||||
|
5 => '5',
|
||||||
|
6 => '6',
|
||||||
|
7 => '7',
|
||||||
|
8 => '8',
|
||||||
|
9 => '9',
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, ind: [usize; 2], val: u8) {
|
||||||
|
if !self.cells[ind[1]][ind[0]].loaded {
|
||||||
|
self.validate(ind, val);
|
||||||
|
self.cells[ind[1]][ind[0]].value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.completed = self
|
||||||
|
.cells
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.all(|cell| !cell.invalid && cell.value != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_sdm(filename: &str) -> Self {
|
||||||
|
let data = read_to_string(filename).expect("failed to read SDM file");
|
||||||
|
let mut cells = [[Cell::default(); SIZE]; SIZE];
|
||||||
|
let mut row = 0;
|
||||||
|
let mut col = 0;
|
||||||
|
for c in data.chars() {
|
||||||
|
if col == SIZE {
|
||||||
|
col = 0;
|
||||||
|
row += 1;
|
||||||
|
}
|
||||||
|
if let Some(v) = c.to_digit(10) {
|
||||||
|
let value = v as u8;
|
||||||
|
cells[row][col] = Cell {
|
||||||
|
value,
|
||||||
|
loaded: value != 0,
|
||||||
|
invalid: false,
|
||||||
|
};
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
cells,
|
||||||
|
completed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&mut self, ind: [usize; 2], val: u8) {
|
||||||
|
let [b, a] = ind;
|
||||||
|
for i in 0..SIZE {
|
||||||
|
if i == a {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.cells[a][i].value == val {
|
||||||
|
self.cells[a][b].invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..SIZE {
|
||||||
|
if i == b {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.cells[i][b].value == val {
|
||||||
|
self.cells[a][b].invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (row, col) = (a / 3, b / 3);
|
||||||
|
for i in 3 * row..3 * row + 3 {
|
||||||
|
for j in 3 * col..3 * col + 3 {
|
||||||
|
if i == a && j == b {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.cells[i][j].value == val {
|
||||||
|
self.cells[a][b].invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cells[a][b].invalid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_sdm() {
|
||||||
|
let got = Gameboard::load_sdm("static/puzzle.sdm");
|
||||||
|
let want = Gameboard::from_cells([
|
||||||
|
[0, 1, 6, 4, 0, 0, 0, 0, 0],
|
||||||
|
[2, 0, 0, 0, 0, 9, 0, 0, 0],
|
||||||
|
[4, 0, 0, 0, 0, 0, 0, 6, 2],
|
||||||
|
[0, 7, 0, 2, 3, 0, 1, 0, 0],
|
||||||
|
[1, 0, 0, 0, 0, 0, 0, 0, 3],
|
||||||
|
[0, 0, 3, 0, 8, 7, 0, 4, 0],
|
||||||
|
[9, 6, 0, 0, 0, 0, 0, 0, 5],
|
||||||
|
[0, 0, 0, 8, 0, 0, 0, 0, 7],
|
||||||
|
[0, 0, 0, 0, 0, 6, 8, 2, 0],
|
||||||
|
]);
|
||||||
|
assert_eq!(got, want);
|
||||||
|
}
|
||||||
|
}
|
57
piston-tutorials/sudoku/src/gameboard_controller.rs
Normal file
57
piston-tutorials/sudoku/src/gameboard_controller.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//! Gameboard controller
|
||||||
|
|
||||||
|
use piston::GenericEvent;
|
||||||
|
|
||||||
|
use crate::Gameboard;
|
||||||
|
|
||||||
|
pub struct GameboardController {
|
||||||
|
pub gameboard: Gameboard,
|
||||||
|
pub selected_cell: Option<[usize; 2]>,
|
||||||
|
cursor_pos: [f64; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameboardController {
|
||||||
|
pub fn new(gameboard: Gameboard) -> GameboardController {
|
||||||
|
GameboardController {
|
||||||
|
gameboard: gameboard,
|
||||||
|
selected_cell: None,
|
||||||
|
cursor_pos: [0.0; 2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event<E: GenericEvent>(&mut self, pos: [f64; 2], size: f64, e: &E) {
|
||||||
|
use piston::input::{Button, Key, MouseButton};
|
||||||
|
|
||||||
|
if let Some(pos) = e.mouse_cursor_args() {
|
||||||
|
self.cursor_pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Button::Mouse(MouseButton::Left)) = e.press_args() {
|
||||||
|
let x = self.cursor_pos[0] - pos[0];
|
||||||
|
let y = self.cursor_pos[1] - pos[1];
|
||||||
|
|
||||||
|
if x >= 0.0 && x < size && y >= 0.0 && y < size {
|
||||||
|
let cell_x = (x / size * 9.0) as usize;
|
||||||
|
let cell_y = (y / size * 9.0) as usize;
|
||||||
|
self.selected_cell = Some([cell_x, cell_y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Button::Keyboard(key)) = e.press_args() {
|
||||||
|
if let Some(ind) = self.selected_cell {
|
||||||
|
match key {
|
||||||
|
Key::D1 => self.gameboard.set(ind, 1),
|
||||||
|
Key::D2 => self.gameboard.set(ind, 2),
|
||||||
|
Key::D3 => self.gameboard.set(ind, 3),
|
||||||
|
Key::D4 => self.gameboard.set(ind, 4),
|
||||||
|
Key::D5 => self.gameboard.set(ind, 5),
|
||||||
|
Key::D6 => self.gameboard.set(ind, 6),
|
||||||
|
Key::D7 => self.gameboard.set(ind, 7),
|
||||||
|
Key::D8 => self.gameboard.set(ind, 8),
|
||||||
|
Key::D9 => self.gameboard.set(ind, 9),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
210
piston-tutorials/sudoku/src/gameboard_view.rs
Normal file
210
piston-tutorials/sudoku/src/gameboard_view.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
//! Gameboard view.
|
||||||
|
|
||||||
|
use graphics::character::CharacterCache;
|
||||||
|
use graphics::types::Color;
|
||||||
|
use graphics::{Context, Graphics};
|
||||||
|
|
||||||
|
use crate::gameboard_controller::GameboardController;
|
||||||
|
|
||||||
|
pub struct GameboardViewSettings {
|
||||||
|
pub position: [f64; 2],
|
||||||
|
pub size: f64,
|
||||||
|
pub background_color: Color,
|
||||||
|
pub border_color: Color,
|
||||||
|
pub board_edge_color: Color,
|
||||||
|
pub section_edge_color: Color,
|
||||||
|
pub cell_edge_color: Color,
|
||||||
|
pub board_edge_radius: f64,
|
||||||
|
pub section_edge_radius: f64,
|
||||||
|
pub cell_edge_radius: f64,
|
||||||
|
pub selected_cell_background_color: Color,
|
||||||
|
pub text_color: Color,
|
||||||
|
pub loaded_cell_background_color: Color,
|
||||||
|
pub invalid_cell_background_color: Color,
|
||||||
|
pub invalid_selected_cell_background_color: Color,
|
||||||
|
pub completed_background_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameboardViewSettings {
|
||||||
|
pub fn new() -> GameboardViewSettings {
|
||||||
|
GameboardViewSettings {
|
||||||
|
position: [10.0; 2],
|
||||||
|
size: 400.0,
|
||||||
|
background_color: [0.8, 0.8, 1.0, 1.0],
|
||||||
|
border_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
board_edge_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
section_edge_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
cell_edge_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
board_edge_radius: 3.0,
|
||||||
|
section_edge_radius: 2.0,
|
||||||
|
cell_edge_radius: 1.0,
|
||||||
|
selected_cell_background_color: [0.9, 0.9, 1.0, 1.0],
|
||||||
|
text_color: [0.0, 0.0, 0.1, 1.0],
|
||||||
|
loaded_cell_background_color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
invalid_cell_background_color: [1.0, 0.0, 0.0, 1.0],
|
||||||
|
invalid_selected_cell_background_color: [1.0, 0.0, 0.5, 1.0],
|
||||||
|
completed_background_color: [0.0, 1.0, 0.0, 1.0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GameboardView {
|
||||||
|
pub settings: GameboardViewSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameboardView {
|
||||||
|
pub fn new(settings: GameboardViewSettings) -> GameboardView {
|
||||||
|
GameboardView { settings: settings }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw<G: Graphics, C>(
|
||||||
|
&self,
|
||||||
|
controller: &GameboardController,
|
||||||
|
glyphs: &mut C,
|
||||||
|
c: &Context,
|
||||||
|
g: &mut G,
|
||||||
|
) where
|
||||||
|
C: CharacterCache<Texture = G::Texture>,
|
||||||
|
{
|
||||||
|
use graphics::{Image, Line, Rectangle, Transformed};
|
||||||
|
|
||||||
|
let ref settings = self.settings;
|
||||||
|
let board_rect = [
|
||||||
|
settings.position[0],
|
||||||
|
settings.position[1],
|
||||||
|
settings.size,
|
||||||
|
settings.size,
|
||||||
|
];
|
||||||
|
|
||||||
|
if controller.gameboard.completed {
|
||||||
|
Rectangle::new(settings.completed_background_color).draw(
|
||||||
|
board_rect,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Rectangle::new(settings.background_color).draw(
|
||||||
|
board_rect,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
|
||||||
|
for i in 0..9 {
|
||||||
|
for j in 0..9 {
|
||||||
|
if controller.gameboard.cells[i][j].loaded {
|
||||||
|
color_cell(
|
||||||
|
settings,
|
||||||
|
[j, i],
|
||||||
|
settings.loaded_cell_background_color,
|
||||||
|
c,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
} else if controller.gameboard.cells[i][j].invalid {
|
||||||
|
color_cell(
|
||||||
|
settings,
|
||||||
|
[j, i],
|
||||||
|
settings.invalid_cell_background_color,
|
||||||
|
c,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ind) = controller.selected_cell {
|
||||||
|
let cell = controller.gameboard.cells[ind[1]][ind[0]];
|
||||||
|
let color = if !cell.loaded {
|
||||||
|
if !cell.invalid {
|
||||||
|
settings.selected_cell_background_color
|
||||||
|
} else {
|
||||||
|
settings.invalid_selected_cell_background_color
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settings.loaded_cell_background_color
|
||||||
|
};
|
||||||
|
color_cell(settings, ind, color, c, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_image = Image::new_color(settings.text_color);
|
||||||
|
let cell_size = settings.size / 9.0;
|
||||||
|
|
||||||
|
for j in 0..9 {
|
||||||
|
for i in 0..9 {
|
||||||
|
if let Some(ch) = controller.gameboard.char([i, j]) {
|
||||||
|
let pos = [
|
||||||
|
settings.position[0] + i as f64 * cell_size + 15.0,
|
||||||
|
settings.position[1] + j as f64 * cell_size + 34.0,
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Ok(character) = glyphs.character(34, ch) {
|
||||||
|
let ch_x = pos[0] + character.left();
|
||||||
|
let ch_y = pos[1] - character.top();
|
||||||
|
let text_image = text_image.src_rect([
|
||||||
|
character.atlas_offset[0],
|
||||||
|
character.atlas_offset[1],
|
||||||
|
character.atlas_size[0],
|
||||||
|
character.atlas_size[1],
|
||||||
|
]);
|
||||||
|
text_image.draw(
|
||||||
|
character.texture,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform.trans(ch_x, ch_y),
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell_edge = Line::new(settings.cell_edge_color, settings.cell_edge_radius);
|
||||||
|
let section_edge = Line::new(settings.section_edge_color, settings.section_edge_radius);
|
||||||
|
|
||||||
|
for i in 0..9 {
|
||||||
|
let x = settings.position[0] + i as f64 / 9.0 * settings.size;
|
||||||
|
let y = settings.position[1] + i as f64 / 9.0 * settings.size;
|
||||||
|
let x2 = settings.position[0] + settings.size;
|
||||||
|
let y2 = settings.position[1] + settings.size;
|
||||||
|
|
||||||
|
let vline = [x, settings.position[1], x, y2];
|
||||||
|
let hline = [settings.position[0], y, x2, y];
|
||||||
|
|
||||||
|
if (i % 3) == 0 {
|
||||||
|
section_edge.draw(vline, &c.draw_state, c.transform, g);
|
||||||
|
section_edge.draw(hline, &c.draw_state, c.transform, g);
|
||||||
|
} else {
|
||||||
|
cell_edge.draw(vline, &c.draw_state, c.transform, g);
|
||||||
|
cell_edge.draw(hline, &c.draw_state, c.transform, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle::new_border(settings.board_edge_color, settings.board_edge_radius).draw(
|
||||||
|
board_rect,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_cell<G: Graphics>(
|
||||||
|
settings: &GameboardViewSettings,
|
||||||
|
ind: [usize; 2],
|
||||||
|
color: [f32; 4],
|
||||||
|
c: &Context,
|
||||||
|
g: &mut G,
|
||||||
|
) {
|
||||||
|
use graphics::Rectangle;
|
||||||
|
|
||||||
|
let cell_size = settings.size / 9.0;
|
||||||
|
let pos = [ind[0] as f64 * cell_size, ind[1] as f64 * cell_size];
|
||||||
|
let cell_rect = [
|
||||||
|
settings.position[0] + pos[0],
|
||||||
|
settings.position[1] + pos[1],
|
||||||
|
cell_size,
|
||||||
|
cell_size,
|
||||||
|
];
|
||||||
|
Rectangle::new(color).draw(cell_rect, &c.draw_state, c.transform, g);
|
||||||
|
}
|
57
piston-tutorials/sudoku/src/main.rs
Normal file
57
piston-tutorials/sudoku/src/main.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//////#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! An Sudoko please.
|
||||||
|
|
||||||
|
extern crate glutin_window;
|
||||||
|
|
||||||
|
use glutin_window::GlutinWindow;
|
||||||
|
use opengl_graphics::{Filter, GlGraphics, GlyphCache, OpenGL, TextureSettings};
|
||||||
|
use piston::event_loop::{EventSettings, Events};
|
||||||
|
use piston::{EventLoop, RenderEvent, WindowSettings};
|
||||||
|
|
||||||
|
pub use crate::gameboard::Gameboard;
|
||||||
|
pub use crate::gameboard_controller::GameboardController;
|
||||||
|
pub use crate::gameboard_view::{GameboardView, GameboardViewSettings};
|
||||||
|
|
||||||
|
mod gameboard;
|
||||||
|
mod gameboard_controller;
|
||||||
|
mod gameboard_view;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opengl = OpenGL::V3_2;
|
||||||
|
let settings = WindowSettings::new("Sudoku", (640, 480))
|
||||||
|
.exit_on_esc(true)
|
||||||
|
.graphics_api(opengl)
|
||||||
|
.vsync(true);
|
||||||
|
let mut window: GlutinWindow = settings.build().expect("could not create window");
|
||||||
|
let mut events = Events::new(EventSettings::new().lazy(true));
|
||||||
|
let mut gl = GlGraphics::new(opengl);
|
||||||
|
|
||||||
|
let args: Vec<_> = std::env::args().collect();
|
||||||
|
let infile = args.get(1).expect("usage: sudoku <sdm-file>");
|
||||||
|
|
||||||
|
let gameboard = Gameboard::load_sdm(infile);
|
||||||
|
let mut gameboard_controller = GameboardController::new(gameboard);
|
||||||
|
let gameboard_view_settings = GameboardViewSettings::new();
|
||||||
|
let gameboard_view = GameboardView::new(gameboard_view_settings);
|
||||||
|
|
||||||
|
let texture_settings = TextureSettings::new().filter(Filter::Nearest);
|
||||||
|
let ref mut glyphs = GlyphCache::new("assets/FiraSans-Regular.ttf", (), texture_settings)
|
||||||
|
.expect("Could not load font");
|
||||||
|
|
||||||
|
while let Some(e) = events.next(&mut window) {
|
||||||
|
gameboard_controller.event(
|
||||||
|
gameboard_view.settings.position,
|
||||||
|
gameboard_view.settings.size,
|
||||||
|
&e,
|
||||||
|
);
|
||||||
|
if let Some(args) = e.render_args() {
|
||||||
|
gl.draw(args.viewport(), |c, g| {
|
||||||
|
use graphics::clear;
|
||||||
|
|
||||||
|
clear([1.0; 4], g);
|
||||||
|
gameboard_view.draw(&gameboard_controller, glyphs, &c, g);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
piston-tutorials/sudoku/static/puzzle-almost-solved.sdm
Normal file
1
piston-tutorials/sudoku/static/puzzle-almost-solved.sdm
Normal file
@@ -0,0 +1 @@
|
|||||||
|
517962483236847915498351762371695248654218397829734156765129834142583679983476520
|
1
piston-tutorials/sudoku/static/puzzle.sdm
Normal file
1
piston-tutorials/sudoku/static/puzzle.sdm
Normal file
@@ -0,0 +1 @@
|
|||||||
|
016400000200009000400000062070230100100000003003087040960000005000800007000006820
|
Reference in New Issue
Block a user