Tekeillä tuota rts-peliä varten (toisessa topiikissa) pieni random funktio joka ei turvautuis liukulukuihin mitenkään. Kokeilin periaatetta että lasketaan ensin ihan normaalein keinoin 2048 valmiiksi arvottua lukua väliltä 0-255 ja laitetaan ne tiedostoon. Jokainen luku esiintyy yhtä monta kertaa mutta Delphin omalla randomilla sekoitettuna.
Nyt sitten tulee pientä ongelmaa kun tuota taulukkoa yrittää ottaa käyttöön. Tarkotuksena on että noiden 2048 sekaisin olevan luvun avulla voi arpoa minkä tahansa (positiivisen) kokonaisluvun.
Kun kutsun arpojaa rnd(101) niin saapi tällasta hajontaa:
http://i39.tinypic.com/2cxzf7.png
Huomaa että osa tietyn välein esiintyy selvästi useammin kun muut. Pikasesti testattuna väli rnd(0)..rnd(16) toimii oikein eli tasanen hajonta eikä yksikään luku tee piikkiä ylös tai alas.
Jos jottain vinkkiä tai huomattavaa tulee mieleen? :)
// Funktio
function Rnd(const n: integer): integer;
var i,sum: integer;
begin
result:=0;
if n<2 then exit;
rnd_seed:=rnd_seed mod 2048;
sum:=0;
repeat
result:=result+_rnd[rnd_seed];
sum:=sum+255;
rnd_seed:=(rnd_seed+1) mod 2048;
until sum>=n;
result:=result*n div (sum+1);
end;
// _rnd[] taulukon Generointi
randomize;
for i:=0 to 2047 do _rnd[i]:=i mod 256;
for k:=1 to 3 do // Sekoitetaan 3 kertaa
for i:=0 to 2047 do begin
j:=random(2048);
temp:=_rnd[i]; _rnd[i]:=_rnd[j]; _rnd[j]:=temp;
end;Miksei se saa käyttää liukulukuja? Pyöristät vaan arvotun summan alaspäin lähimpään kokonaislukuun.
Alkup. viesti oli siis Tuolla.
Kävi ilmi että eri prosessorit laskee liukuluvuilla eri tavalla. Olen melko varma että kielien yleiset randomit turvaa liukulukuihin jollain tapaa.
Erilaisia variaatioita on tullu myös kokeiltua ja funktio kirjotettu uudestaan tyhjästä melko tuloksetta. 200 luvulla voi jo saada aikaan jonkunlaisen "sahanterän" mutta 3000 on jo karua katseltavaa. Pitäskö mun vaan käyttää suosiolla suurempia lukuja taulukossa tai enemmän lukuja? Jotenkin vaan luulen että se on ns. purkkaratkaisu joka kostautuu kun taas etsii 10 kertaa suurempia satunnaislukuja.
Objektien koordinaatit itse on helppo esittää kokonaisluvuilla. 4 tavuun (sama kuin tyyppi single) mahtuu luku jonka koko on 4 miljardia eikä tuon pelin karttakoko tule olemaan muutamaa tuhatta suurempi. Saan siis rauhassa kertoa koordinaatit 0.001:llä piirtovaiheessa ja olettaa että 1000 kokonaislukuna vastaa lukua 1 desimaaleina.
User137 kirjoitti:
Olen melko varma että kielien yleiset randomit turvaa liukulukuihin jollain tapaa.
Ööö... ei? Yksinkertaisimmat toimivat näin
http://en.wikipedia.org/wiki/
Ainakin voit koodata oman random-funktion samalla tavalla, jos epäilyttää.
Onhan käsite siemenluku tuttu?
http://www.delphibasics.co.uk/RTL.asp?Name=RandSeed
http://www.cplusplus.com/reference/clibrary/
No joo, näyttäis että Borland on käyttäny just tuota linearia vanhoista TP ajoista Delphi 7 asti ainakin (ei ihan virallisesta lähteestä). Homma jatkuu kumminkin sillä...
Käsittääkseni jos tuota omaa ideaa olis jatkanu ja päästä helpolla niin niiden lukujen olis tullu olla vähintään yhtä suuria kuin suurin luku mitä sillä generoidaan ja jottei samankaltaisuutta ole liikaa niin useampia jaksoja. Toisinsanoen olis vieny muistia liikaa. Olis kasvattanu seediä aina yhdellä ja skaalannu sillä kohtaa esiintyvän taulukon luvun halutulla arvolla. Toisaalta jos haluaa rajoittaa muistin käyttöä niin käyttäis pienempiä lukuja sillä menetyksellä että osa luvuista jää esiintymättä kun kysytty arvo on suurempi (mikä ei välttämättä haittaa).
Miten kävisi multiply-with-carry (MWC) random-funktio toteutus?
Alla on Micho Durdevichin OS/2 PM-ohjelmointi esimerkistä kopioitu assembly toteutus:
mov eax, 0x4019CF16 mul random_var add eax, random_var[4] mov random_var, eax adc edx, 0 mov random_var[4], edx
Kehuvat hyväksi ja nopeaksi sellaista kuin Mersenne twister. Sivun lopussa on myös pseudokoodi, joka vääntyy Pascaliksi aika näppärästi.
Complimentary-Multiply-With-Carry on aika paljon yksinkertaisempi ja nopeampi kuin Mersenne Twister, mutta silti riittävän satunnainen ja erittäin pitkäjaksoinen.
Ikiomaa algoritmia ei oikein kannata ruveta väsäämään, ellei ole hallussa aikas paljon teoriaa ja tutkimusta. Omaan peliin riittää varmasti kirjaston perus-random. Nettipokerissa tai jossain Monte Carlo -laskennassa on parasta siirtyä käyttämään jotakin ihan totisesti oikeata algoritmitoteutusta, joka on myös testattu oikein.
Alla esimerkkinä aikaisemmin mainitsemani MWC random-funktio OpenWatcom C-kutsuttavana assembler toteutuksena.
rand1.asm
.386p
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
public _random_var1
public _random_var2
_random_var1 dd 8
_random_var2 dd 5
_DATA ENDS
DGROUP GROUP _DATA
_TEXT SEGMENT PARA PUBLIC USE32 'CODE'
ASSUME CS:_TEXT, DS:_DATA
public Randomize_
Randomize_ proc near
push ebx
push edx
cmp eax, 0
jz @random_exit
cmp eax, 0xFFFFFFFF
jz @random_exit
mov ebx, eax
mov eax, 0x4019CF16
mul _random_var1
add eax, _random_var1[4]
mov _random_var1, eax
adc edx, 0
mov _random_var1[4], edx
xor edx, edx
inc ebx
div ebx
xchg edx, eax
@random_exit:
pop edx
pop ebx
ret
Randomize_ endp
_TEXT ends
endJos haluat testata, niin tee uusi projekti. Määritä alustaksi 32-bittinen DOS (Causeway -tai dos4gw-extender) ja liitä ylläolevan tiedoston lisäksi projektiin allaolevat tiedostot:
buftoscr.asm
.386p
extrn _Screen:dword
extrn _ScreenBuffer:dword
_TEXT SEGMENT PARA PUBLIC USE32 'CODE'
ASSUME CS:_TEXT, DS:_DATA
public BufferToScreen_
BufferToScreen_ proc near
push esi
push edi
; Kopioi näyttöpuskurin sisällön näyttömuistiin 32 bittiä kerrallaan!!!
;
cld
mov edi, _Screen ; osoitin videomuistiin
mov esi, _ScreenBuffer ; osoitin puskuriin
mov cx,((320*200)/4) ; kopioitavan määrä
mov dx,3dah ; VGA portti ja indeksi (Input Status Register)
@retrace:
in al,dx ; lue VGA portti
and al,8 ; selvitä tahdistuksen tila
jz @retrace ; odota näytön tahdistusta
rep movsd ; kopioi 32-bittiä kerrallaan
pop edi
pop esi
ret
BufferToScreen_ endp
public ClearBuffer_
ClearBuffer_ proc near
push edi
cld
mov edi, _ScreenBuffer
mov eax,0 ; täyttöväri
mov cx,((320*200)/4) ; kopioitavan määrä
rep stosd ; kopioi 32-bittiä kerrallaan
pop edi
ret
ClearBuffer_ endp
_TEXT ends
endmain.c
//
// OpenWatcom versio spritekääntäjästä
//
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
#define SPRITE_WIDTH 24
#define SPRITE_HEIGHT 16
typedef unsigned char uchar;
typedef void near (*CompiledSprite)(uchar *,int,int);
CompiledSprite CompileSprite(uchar *pBitmap, int width, int height);
CompiledSprite CompileSprite(uchar *pBitmap, int width, int height)
{
int row, column;
int b1, b2, b3, b4;
int dwords=0, words=0, bytes=0;
unsigned int Offset, BufferSize, Index=0;
uchar *CodeBuffer;
// Selvitetään kuinka monta tavua kerrallaan voidaan kopioida
for(row=0; row < height; row++) {
Offset = row * width;
for(column=0; column < width; column++) {
b1 = (pBitmap[Offset+column] != 0 ) ? 1 : 0;
if((column+1) < width) {
b2 = (pBitmap[Offset+column+1] != 0 ) ? 1 : 0;
if((column+2) < width) {
b3 = (pBitmap[Offset+column+2] != 0 ) ? 1 : 0;
if((column+3) < width) {
b4 = (pBitmap[Offset+column+3] != 0 ) ? 1 : 0;
}
else b4 = 0;
}
else b3 = 0;
}
else b2 = 0;
if(b1) {
if(b2) {
if(b3) {
if(b4) {
dwords++;
column+=3;
}
else {
words++;
bytes++;
column+=2;
}
}
else {
words++;
column++;
}
}
else {
bytes++;
}
}
} // Lopeta sarake looppi
} // Lopeta rivi looppi
// Lasketaan tarvittava puskurin koko
BufferSize = (10 * dwords) + (9 * words) + (7 * bytes) + 15;
// Varataan tilaa koodipuskurille
CodeBuffer = (uchar *) malloc(BufferSize);
if(!CodeBuffer) return (CompiledSprite)CodeBuffer;
// Käydään kuvatieto läpi uudestaan ja kirjoitetaan puskuriin
// konekielinen piirtorutiini
// Parametrit rekistereistä
CodeBuffer[Index++] = 0x89; // mov ecx,ebx
CodeBuffer[Index++] = 0xd9;
CodeBuffer[Index++] = 0xc1; // shl ebx,08h
CodeBuffer[Index++] = 0xe3;
CodeBuffer[Index++] = 0x08;
CodeBuffer[Index++] = 0xc1; // shl ecx,06h
CodeBuffer[Index++] = 0xe1;
CodeBuffer[Index++] = 0x06;
CodeBuffer[Index++] = 0x01; // add eax,ebx
CodeBuffer[Index++] = 0xd8;
CodeBuffer[Index++] = 0x01; // add eax,ecx
CodeBuffer[Index++] = 0xc8;
CodeBuffer[Index++] = 0x01; // add eax,edx
CodeBuffer[Index++] = 0xd0;
// Kuvatieto
for(row=0; row < height; row++) {
Offset = row * width;
for(column=0; column < width; column++) {
b1 = (pBitmap[Offset+column] != 0 ) ? 1 : 0;
if((column+1) < width) {
b2 = (pBitmap[Offset+column+1] != 0 ) ? 1 : 0;
if((column+2) < width) {
b3 = (pBitmap[Offset+column+2] != 0 ) ? 1 : 0;
if((column+3) < width) {
b4 = (pBitmap[Offset+column+3] != 0 ) ? 1 : 0;
}
else b4 = 0;
}
else b3 = 0;
}
else b2 = 0;
if(b1) {
if(b2) {
if(b3) {
if(b4) {
// mov dword ptr [eax+offset32],xxxxxxxx
CodeBuffer[Index++] = 0xc7;
CodeBuffer[Index++] = 0x80;
// 32-bit offset
CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF);
CodeBuffer[Index++] = ((row * 320 + column) >> 8);
CodeBuffer[Index++] = 0x00;
CodeBuffer[Index++] = 0x00;
// pixel data
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column];
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+1];
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+2];
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+3];
column+=3;
}
else {
// mov word ptr [eax+offset32],xxxx
CodeBuffer[Index++] = 0x66;
CodeBuffer[Index++] = 0xc7;
CodeBuffer[Index++] = 0x80;
// 32-bit offset
CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF);
CodeBuffer[Index++] = ((row * 320 + column) >> 8);
CodeBuffer[Index++] = 0x00;
CodeBuffer[Index++] = 0x00;
// pixel data
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column];
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+1];
// mov byte ptr [eax+offset32],xx
CodeBuffer[Index++] = 0xc6;
CodeBuffer[Index++] = 0x80;
// 32-bit offset
CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF);
CodeBuffer[Index++] = ((row * 320 + column) >> 8);
CodeBuffer[Index++] = 0x00;
CodeBuffer[Index++] = 0x00;
// pixel data
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+2];
column+=2;
}
}
else {
// mov word ptr [eax+offset32],xxxx
CodeBuffer[Index++] = 0x66;
CodeBuffer[Index++] = 0xc7;
CodeBuffer[Index++] = 0x80;
// 32-bit offset
CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF);
CodeBuffer[Index++] = ((row * 320 + column) >> 8);
CodeBuffer[Index++] = 0x00;
CodeBuffer[Index++] = 0x00;
// pixel data
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column];
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+1];
column++;
}
}
else {
// mov byte ptr [eax+offset32],xx
CodeBuffer[Index++] = 0xc6;
CodeBuffer[Index++] = 0x80;
// 32-bit offset
CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF);
CodeBuffer[Index++] = ((row * 320 + column) >> 8);
CodeBuffer[Index++] = 0x00;
CodeBuffer[Index++] = 0x00;
// pixel data
CodeBuffer[Index++] = (uchar) pBitmap[Offset + column];
}
}
} // Lopeta sarake looppi
} // Lopeta rivi looppi
// paluu
CodeBuffer[Index++] = 0xc3; // retn
return (CompiledSprite)CodeBuffer;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// Seuraava on vain testiä varten ...
/////////////////////////////////////////////////////////////////////////////////////////////////
void SetMode13h(void);
#pragma aux SetMode13h = \
" mov ax,13h " \
" int 10h " \
modify [ax];
void SetMode03h(void);
#pragma aux SetMode03h = \
" mov ax,03h " \
" int 10h " \
modify [ax];
extern int Randomize(int);
extern void ClearBuffer(void);
extern void BufferToScreen(void);
extern int random_var1;
extern int random_var2;
struct dostime_t time;
uchar *Screen;
uchar *ScreenBuffer;
CompiledSprite testi;
uchar Sprite_data[] = {
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
00,00,00,00,00,00,16,16,16,16,16,16,16,16,16,16,16,16,16,00,00,00,00,00,
00,00,00,00,16,16,16,14,14,14,14,14,14,14,14,14,14,14,16,16,16,00,00,00,
00,00,00,16,16,14,14,14,16,14,14,14,14,14,14,16,14,14,14,14,16,16,00,00,
00,16,16,16,14,14,14,16,16,16,14,14,14,14,16,16,16,14,14,14,14,16,16,00,
00,16,14,14,14,14,14,16,16,16,14,14,14,14,16,16,16,14,14,14,14,14,16,00,
00,16,14,14,14,14,14,14,16,14,14,14,14,14,14,16,14,14,14,14,14,14,16,00,
00,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,00,
00,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,00,
00,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,00,
00,16,14,14,14,16,14,14,14,14,14,14,14,14,14,14,14,14,16,14,14,14,16,00,
00,16,14,14,14,14,16,14,14,14,14,14,14,14,14,14,14,16,14,14,14,14,16,00,
00,16,16,16,14,14,14,16,16,16,16,16,16,16,16,16,16,14,14,14,16,16,16,00,
00,00,00,16,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,16,00,00,00,
00,00,00,00,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,00,00,00,00,
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00 };
int main(void)
{
_dos_gettime( &time );
random_var1 = time.second;
random_var2 = time.minute;
testi=(CompiledSprite)CompileSprite(Sprite_data, SPRITE_WIDTH, SPRITE_HEIGHT);
ScreenBuffer=malloc(SCREEN_WIDTH*SCREEN_HEIGHT);
if(!ScreenBuffer)
{
printf("Ei tarpeeksi muistia näyttöpuskurille\n");
exit(1);
}
ClearBuffer();
Screen = (uchar *) 0xA0000; // osoitin videomuistiin
SetMode13h();
while(!kbhit()) {
testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT));
testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT));
testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT));
testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT));
testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT));
BufferToScreen();
}
free(ScreenBuffer);
SetMode03h();
return 0;
}Ohjelman pitäisi piirtää melko vauhdikkaasti hymynaamoja ruudulle satunnaisille paikoille. Ai niin... kyseessä ei sitten ole mikään esimerkki hyvännäköisestä C-ohjelmointi tavasta.
ja alla dailywtf:sta
function random(){
return 4; // luku on valittu täysin satunnaisesti
}Aihe on jo aika vanha, joten et voi enää vastata siihen.